/opt/imunify360/venv/share/imunify360/core-releases
NameSizeModeActions
imunify-core-release.tar75059200644editdlrm
Edit: /opt/imunify360/venv/share/imunify360/core-releases/imunify-core-release.tar (7505920B)
defence360agent/0000755000000000000000000000000000000000000010546 5ustar defence360agent/__init__.py0000644000000000000000000000044700000000000012664 0ustar import os as _os # Import machinery records the path through site-packages, including symlinks. # Resolve it once so lazy submodule imports stay on the core release selected # when this process first imported defence360agent. __path__ = [_os.path.realpath(path) for path in __path__] del _os defence360agent/__main__.py0000644000000000000000000000005300000000000012636 0ustar from defence360agent import cli cli.run() defence360agent/__pycache__/0000755000000000000000000000000000000000000012756 5ustar defence360agent/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000075700000000000020167 0ustar r_j'(ddlZdeDZ[dS)NcLg|]!}tj|"S)_ospathrealpath).0rs M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__init__.py r s( 9 9 9CH  d # # 9 9 9)osr__path__rr r rs- : 9 9 9 9CCr defence360agent/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000075700000000000017230 0ustar r_j'(ddlZdeDZ[dS)NcLg|]!}tj|"S)_ospathrealpath).0rs M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__init__.py r s( 9 9 9CH  d # # 9 9 9)osr__path__rr r rs- : 9 9 9 9CCr defence360agent/__pycache__/__main__.cpython-311.opt-1.pyc0000644000000000000000000000044100000000000020136 0ustar r_j+0ddlmZejdS))cliN)defence360agentrrunM/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__main__.pyr s( rdefence360agent/__pycache__/__main__.cpython-311.pyc0000644000000000000000000000044100000000000017177 0ustar r_j+0ddlmZejdS))cliN)defence360agentrrunM/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/__main__.pyr s( rdefence360agent/__pycache__/_version.cpython-311.opt-1.pyc0000644000000000000000000000031700000000000020244 0ustar r_jS dZdS)z8.12.1N) __version__M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/_version.pyrs rdefence360agent/__pycache__/_version.cpython-311.pyc0000644000000000000000000000031700000000000017305 0ustar r_jS dZdS)z8.12.1N) __version__M/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/_version.pyrs rdefence360agent/__pycache__/defence360.cpython-311.opt-1.pyc0000644000000000000000000001516100000000000020245 0ustar r_jddlZddlZddlZddlZddlmZddlZddlm Z ddl m Z ddl mZmZddlmZddlmZmZmZmZmZddlmZmZdd lmZejeZed Z d Z!d Z"dS) N)Path)Core) ResponseError)SUCCESS SocketError) is_root_user) EXIT_CODESEXITCODE_GENERAL_ERROR print_errorprint_responseprint_warnings) EnvParsercreate_cli_parser) flush_sentryz5/var/lib/rpm-state/imunify360-transaction-in-progressctjtjtjj|t}| |}|j stj drHtjj |j ptj d|jr)tjj|jt!|dr&ddlm}t'|||jdSt!|drPt!|dr? ||}t-jtj |j|j|}|jd i||\}}t7|t9|t:kr"t=|j||j|j n=tC|||j|j tEj#tH|dSdS#tJ$rU} t=dd d &| i|j|j tEj#tNYd} ~ dSd} ~ wwxYwt'|(dS) N)argsIMUNIFY360_LOGGING_CONFIG_FILEcompletions_commandr)generate_completionsendpointgenerate_endpoint_params)excludeitemsz ERROR: {}))osumaskConfig FILE_UMASKdefence360agent internalslogger reconfigurer parse_args log_configenvirongetupdate_logging_config_from_fileconsole_log_levelsetConsoleLogLevelhasattr!defence360agent.utils.completionsrprintshellrrparsecommandenvvar_parameter_optionsrr rrr jsonverboser sysexitr rformatr format_help) rpc_handlers_initcli_argsparserrr cli_kwargs envvar_kwargsresultdataes O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/defence360.pymainr@sHV $00222  F   (  + +D  "*..)IJJ !(HH O Orz~~.NOO     !(;;  "   t*++JJJJJJ ""64:66777tZ  $WT3M%N%N$ -66t<}td|t jtYd}~nd}~wt $r}t"rPtd|t dt jt jtn3td t jtYd}~nFd}~wt($r6td t jtYnwxYwt+jdS#t+jwxYw) Nz'%s could be used by the root user only!z/Imunify360 CLI is unavailable for non-root user)filezUser pressed Ctrl+C, exiting...zResponse error: %sz"RPM transaction is in progress. %szPRPM transaction is in progress. Please, wait until it is finished and try again.z5Unknown error happened. See logs for more information)rr!inforNAMEr,r3stderrr4r r@argvKeyboardInterruptwarningrerror ImportErrorRPM_TRANSACTION_LOCKexists exception Exceptionasyncioget_event_loopclose)r7r>s r? entrypointrSQs< >>) =v{KKK =CJ     '((()  ---- )))8999 '((((( ))) )1--- '((((((((  - - -  & & ( ( - LL=q A A A *Z     H+ , , , ,   G    H+ , , , ))) C    '((((( )   &&(((((  &&((((s\)"B H* =H H* H4D H* HBF=8H*=AH=H*?HH**'I)#rPloggingrr3pathlibr defence360agent.internals.loggerr defence360agent.contracts.configrr$defence360agent.rpc_tools.exceptionsrdefence360agent.simple_rpcrrdefence360agent.utilsrdefence360agent.utils.clir r r r r defence360agent.utils.parsersrrdefence360agent.sentryr getLogger__name__r!rLr@rSrr?rasZ '''';;;;;;>>>>>>;;;;;;;;......GFFFFFFF//////  8 $ $t; 1$1$1$h%)%)%)%)%)r`defence360agent/__pycache__/defence360.cpython-311.pyc0000644000000000000000000001516100000000000017306 0ustar r_jddlZddlZddlZddlZddlmZddlZddlm Z ddl m Z ddl mZmZddlmZddlmZmZmZmZmZddlmZmZdd lmZejeZed Z d Z!d Z"dS) N)Path)Core) ResponseError)SUCCESS SocketError) is_root_user) EXIT_CODESEXITCODE_GENERAL_ERROR print_errorprint_responseprint_warnings) EnvParsercreate_cli_parser) flush_sentryz5/var/lib/rpm-state/imunify360-transaction-in-progressctjtjtjj|t}| |}|j stj drHtjj |j ptj d|jr)tjj|jt!|dr&ddlm}t'|||jdSt!|drPt!|dr? ||}t-jtj |j|j|}|jd i||\}}t7|t9|t:kr"t=|j||j|j n=tC|||j|j tEj#tH|dSdS#tJ$rU} t=dd d &| i|j|j tEj#tNYd} ~ dSd} ~ wwxYwt'|(dS) N)argsIMUNIFY360_LOGGING_CONFIG_FILEcompletions_commandr)generate_completionsendpointgenerate_endpoint_params)excludeitemsz ERROR: {}))osumaskConfig FILE_UMASKdefence360agent internalslogger reconfigurer parse_args log_configenvirongetupdate_logging_config_from_fileconsole_log_levelsetConsoleLogLevelhasattr!defence360agent.utils.completionsrprintshellrrparsecommandenvvar_parameter_optionsrr rrr jsonverboser sysexitr rformatr format_help) rpc_handlers_initcli_argsparserrr cli_kwargs envvar_kwargsresultdataes O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/defence360.pymainr@sHV $00222  F   (  + +D  "*..)IJJ !(HH O Orz~~.NOO     !(;;  "   t*++JJJJJJ ""64:66777tZ  $WT3M%N%N$ -66t<}td|t jtYd}~nd}~wt $r}t"rPtd|t dt jt jtn3td t jtYd}~nFd}~wt($r6td t jtYnwxYwt+jdS#t+jwxYw) Nz'%s could be used by the root user only!z/Imunify360 CLI is unavailable for non-root user)filezUser pressed Ctrl+C, exiting...zResponse error: %sz"RPM transaction is in progress. %szPRPM transaction is in progress. Please, wait until it is finished and try again.z5Unknown error happened. See logs for more information)rr!inforNAMEr,r3stderrr4r r@argvKeyboardInterruptwarningrerror ImportErrorRPM_TRANSACTION_LOCKexists exception Exceptionasyncioget_event_loopclose)r7r>s r? entrypointrSQs< >>) =v{KKK =CJ     '((()  ---- )))8999 '((((( ))) )1--- '((((((((  - - -  & & ( ( - LL=q A A A *Z     H+ , , , ,   G    H+ , , , ))) C    '((((( )   &&(((((  &&((((s\)"B H* =H H* H4D H* HBF=8H*=AH=H*?HH**'I)#rPloggingrr3pathlibr defence360agent.internals.loggerr defence360agent.contracts.configrr$defence360agent.rpc_tools.exceptionsrdefence360agent.simple_rpcrrdefence360agent.utilsrdefence360agent.utils.clir r r r r defence360agent.utils.parsersrrdefence360agent.sentryr getLogger__name__r!rLr@rSrr?rasZ '''';;;;;;>>>>>>;;;;;;;;......GFFFFFFF//////  8 $ $t; 1$1$1$h%)%)%)%)%)r`defence360agent/__pycache__/migrate.cpython-311.opt-1.pyc0000644000000000000000000002246000000000000020053 0ustar r_j dZddlZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZddlZddlmZddlmZdd lmZdd lmZdd lmZdd lmZdd lmZddlm Z ddl!m"Z"m#Z#m$Z$ddl%m&Z&e e'Z(dZ)ej*de+de,fdZ-dedee+fdZ.e/fdee+de/e/e+e+fdffdZ0dZ1deddZ2e'dkr e2dSdS) zbThis module import peewee_migrate and apply migrations, for Imunify-AV it's entrypoint for serviceN)Iterable) getLogger)migrator)SqliteExtDatabase)app) configure)Core)Model)Router)systemd_notifier)db) tls_check)write_pid_fileIM360_RESIDENT_PID_PATHcleanup_pid_file)recreate_schema_modelsz/usr/bin/imunify-residentlog_msgreraisec#vK dVdS#t$r$t|| |rYdSwxYw)z Logs error in case of exception. Depending on `reraise`: - re-raise exception and don't include exception info in the log operation - do not re-raise exception and include exception info in the log operation N)exc_info) Exceptionloggererror)rrs L/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrate.py exc_handlerr)se   W7{ 333      s  *88r migrations_dirsc~t||t}tt_|dS)z4Apply migrations: restructure db, config files, etc.)rrN)r rrLOGGERrun)r rrouters rapply_migrationsr!:s; 'FHO JJLLLLL attached_dbs.ctjtjtjg}|D]1\}}tjd||f||2 t dtj tj j tjd5tdd5t!t|dddn #1swxYwYdddn #1swxYwYt dtjd5td d 5t#t|td d 5t!t|dddn #1swxYwYdddn #1swxYwYdddn #1swxYwYtjdS#tjwxYw) a> Apply migrations and recreate attached databases. The workflow: 1. Apply migrations 2. Regardless whether the migrations were applied - recreate attached databases 3. If the recreation of the attached databases was successful - apply migrations again - this is done to verify that migrations will successfully apply in future for the recreated databases - the recreation + the migrations in this step are within the same transaction, so databases will only be recreated if the migrations can applied after the recreation. z ATTACH ? AS ?zApplying database migrations... EXCLUSIVEzError applying migrationsF)rNz Recreating attached databases...z#Error recreating attached databasesTz=Error applying migrations after recreating attached databases)rreset db_instanceinitr PATH execute_sqlappendrinfor notify AgentState MIGRATINGatomicrr!rclose)rr#attached_schemasdb_path schema_names rprepare_databasesr5Hs2"OUZ    ,--';1GHHH ,,,, 5666 0 ; EFFF   , , ; ;k '/ / /  ; ; [/ : : : ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;  6777   , , ? ?k 14/ / / ? ? #;0@ A A A ? ? !o>>>  ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"  s)AG':D C." D.C2 2D5C2 6D9 G'D  G' D  1G'>G'F07F F0F F0 F !F0$ G0F4 4G7F4 8G; G'G  G'G G''G<ctd|tdtjt jtjtdtjddS)Nz$Received signal %s in signal_handlerz0waiting %d seconds so that migrations can finishExitingr) rwarningr %SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECStimesleepr,sysexit)sig_s rsignal_handlerr@~sn NN93??? NN: 2 Jt9::: KK HQKKKKKr"defence360agent) start_pkgrctjtjtjfD]}tj|t |dkrt t tjtj |tj j tjt"t$jt$jf}||t/jt.jjtdt/jt.jj|dkrqtjdtdtjt@t@gtBj"ddzdStjtBj#tBj#d d $|gtBj"ddzdS#tJ$r!|dkrtMt YdSYdSwxYw) zoEntry point for Imunify-AV service. Apply migrations, and then replace process with {start_pkg}.run module.zim360.run_resident)targetargszStarting main process...T)exist_okzRun imunify-resident serviceNz-mz{})'signalSIGINTSIGTERMSIGHUPr@rrosumaskr FILE_UMASKrA internalsr reconfigure threadingThreadr5rMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSstartjoinr r-r.READYr,STARTING GO_FLAG_FILEtouchexecvGO_SERVICE_NAMEr<argv executableformatrr)rBrr>migration_threads rrrs* v~v}=++ c>****"6 , , , 2 3 3 3 !!! !(44666$+$%s'BC        0 ; ABBB ./// 0 ; DEEE , , ,   # #T # 2 2 2 KK6 7 7 7 H#(122,      Ht{{9'='=>!""M      666 , , , 4 5 5 5 5 5 5 - , ,6sFH-AH--$II__main__)3__doc__ contextlibrLr<rHrQr:collections.abcrloggingrpeewee_migraterplayhouse.sqlite_extr defence360agent.internals.loggerrAdefence360agent.applicationr$defence360agent.application.settingsr defence360agent.contracts.configr r defence360agent.routerr defence360agent.subsysr defence360agent.model.instancer r'defence360agent.modelrdefence360agent.utilsrrrdefence360agent.utils.check_dbr__name__rr\contextmanagerstrboolrr!tupler5r@rr"rrxs  $$$$$$######222222''''++++++::::::111111222222))))))333333<<<<<<++++++  8  -   t     * Xc]     1622c]2c3h,-2222l')(6(6(6(6(6V zCEEEEEr"defence360agent/__pycache__/migrate.cpython-311.pyc0000644000000000000000000002246000000000000017114 0ustar r_j dZddlZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZddlZddlmZddlmZdd lmZdd lmZdd lmZdd lmZdd lmZddlm Z ddl!m"Z"m#Z#m$Z$ddl%m&Z&e e'Z(dZ)ej*de+de,fdZ-dedee+fdZ.e/fdee+de/e/e+e+fdffdZ0dZ1deddZ2e'dkr e2dSdS) zbThis module import peewee_migrate and apply migrations, for Imunify-AV it's entrypoint for serviceN)Iterable) getLogger)migrator)SqliteExtDatabase)app) configure)Core)Model)Router)systemd_notifier)db) tls_check)write_pid_fileIM360_RESIDENT_PID_PATHcleanup_pid_file)recreate_schema_modelsz/usr/bin/imunify-residentlog_msgreraisec#vK dVdS#t$r$t|| |rYdSwxYw)z Logs error in case of exception. Depending on `reraise`: - re-raise exception and don't include exception info in the log operation - do not re-raise exception and include exception info in the log operation N)exc_info) Exceptionloggererror)rrs L/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrate.py exc_handlerr)se   W7{ 333      s  *88r migrations_dirsc~t||t}tt_|dS)z4Apply migrations: restructure db, config files, etc.)rrN)r rrLOGGERrun)r rrouters rapply_migrationsr!:s; 'FHO JJLLLLL attached_dbs.ctjtjtjg}|D]1\}}tjd||f||2 t dtj tj j tjd5tdd5t!t|dddn #1swxYwYdddn #1swxYwYt dtjd5td d 5t#t|td d 5t!t|dddn #1swxYwYdddn #1swxYwYdddn #1swxYwYtjdS#tjwxYw) a> Apply migrations and recreate attached databases. The workflow: 1. Apply migrations 2. Regardless whether the migrations were applied - recreate attached databases 3. If the recreation of the attached databases was successful - apply migrations again - this is done to verify that migrations will successfully apply in future for the recreated databases - the recreation + the migrations in this step are within the same transaction, so databases will only be recreated if the migrations can applied after the recreation. z ATTACH ? AS ?zApplying database migrations... EXCLUSIVEzError applying migrationsF)rNz Recreating attached databases...z#Error recreating attached databasesTz=Error applying migrations after recreating attached databases)rreset db_instanceinitr PATH execute_sqlappendrinfor notify AgentState MIGRATINGatomicrr!rclose)rr#attached_schemasdb_path schema_names rprepare_databasesr5Hs2"OUZ    ,--';1GHHH ,,,, 5666 0 ; EFFF   , , ; ;k '/ / /  ; ; [/ : : : ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;  6777   , , ? ?k 14/ / / ? ? #;0@ A A A ? ? !o>>>  ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?"  s)AG':D C." D.C2 2D5C2 6D9 G'D  G' D  1G'>G'F07F F0F F0 F !F0$ G0F4 4G7F4 8G; G'G  G'G G''G<ctd|tdtjt jtjtdtjddS)Nz$Received signal %s in signal_handlerz0waiting %d seconds so that migrations can finishExitingr) rwarningr %SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECStimesleepr,sysexit)sig_s rsignal_handlerr@~sn NN93??? NN: 2 Jt9::: KK HQKKKKKr"defence360agent) start_pkgrctjtjtjfD]}tj|t |dkrt t tjtj |tj j tjt"t$jt$jf}||t/jt.jjtdt/jt.jj|dkrqtjdtdtjt@t@gtBj"ddzdStjtBj#tBj#d d $|gtBj"ddzdS#tJ$r!|dkrtMt YdSYdSwxYw) zoEntry point for Imunify-AV service. Apply migrations, and then replace process with {start_pkg}.run module.zim360.run_resident)targetargszStarting main process...T)exist_okzRun imunify-resident serviceNz-mz{})'signalSIGINTSIGTERMSIGHUPr@rrosumaskr FILE_UMASKrA internalsr reconfigure threadingThreadr5rMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSstartjoinr r-r.READYr,STARTING GO_FLAG_FILEtouchexecvGO_SERVICE_NAMEr<argv executableformatrr)rBrr>migration_threads rrrs* v~v}=++ c>****"6 , , , 2 3 3 3 !!! !(44666$+$%s'BC        0 ; ABBB ./// 0 ; DEEE , , ,   # #T # 2 2 2 KK6 7 7 7 H#(122,      Ht{{9'='=>!""M      666 , , , 4 5 5 5 5 5 5 - , ,6sFH-AH--$II__main__)3__doc__ contextlibrLr<rHrQr:collections.abcrloggingrpeewee_migraterplayhouse.sqlite_extr defence360agent.internals.loggerrAdefence360agent.applicationr$defence360agent.application.settingsr defence360agent.contracts.configr r defence360agent.routerr defence360agent.subsysr defence360agent.model.instancer r'defence360agent.modelrdefence360agent.utilsrrrdefence360agent.utils.check_dbr__name__rr\contextmanagerstrboolrr!tupler5r@rr"rrxs  $$$$$$######222222''''++++++::::::111111222222))))))333333<<<<<<++++++  8  -   t     * Xc]     1622c]2c3h,-2222l')(6(6(6(6(6V zCEEEEEr"defence360agent/__pycache__/router.cpython-311.opt-1.pyc0000644000000000000000000000672500000000000017751 0ustar r_jXdZddlZddlmZddlmZddlmZdgZ GddeZdS)z!Provide Router for db migrations.N)suppress)Router)voidrc>eZdZdZfdZedZdZxZS)rzALike peewee_migrate.Router but supports multiple migrations dirs.c Ztj|fd|di|||_dS)N migrate_dirr)super__init__migrations_dirs)selfdatabaser kwargs __class__s K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/router.pyr zRouter.__init__s:LLq/ALVLLL.c0jD]P}tj|s/jd|tj|Qg}jD]2}|tfdtj|Dz }3|S)zScan migrations in file system.z'Migration directory: %s does not exist.c3|K|]6}j||dtd V7dS)N.py)filemaskmatchlen).0fr s r zRouter.todo..s^&&=&&q))&-SZZK- &&&&&&r) r ospathexistsloggerwarnmakedirssortedlistdir)r rmigration_namess` rtodoz Router.todos / ) )K7>>+.. )   ={ K(((/  K v&&&&K00&&&   OO rc i}|jD]}tt5ttj||dz5}t|ddd}t||dddn #1swxYwYdddn #1swxYwY| dt| dtfS) zRead migration from file.rzexecT) dont_inheritNmigraterollback) r rFileNotFoundErroropenrrjoincompilereadr&getr)r namescoperrcodes rr.z Router.read&sR/ & &K+,, & &"',,{D5LAABB&a"*f4Du%%% &&&&&&&&&&&&&&& & & & & & & & & & & & & & & & yyD))599Z+F+FFFs51B*6B B*B B*B B**B. 1B. ) __name__ __module__ __qualname____doc__r propertyr$r. __classcell__)rs@rrr soKK/////X" G G G G G G Gr) r6r contextlibrpeewee_migrater PeeweeRouterpeewee_migrate.routerr__all__rrr?s'' 111111&&&&&& *$G$G$G$G$G\$G$G$G$G$Grdefence360agent/__pycache__/router.cpython-311.pyc0000644000000000000000000000672500000000000017012 0ustar r_jXdZddlZddlmZddlmZddlmZdgZ GddeZdS)z!Provide Router for db migrations.N)suppress)Router)voidrc>eZdZdZfdZedZdZxZS)rzALike peewee_migrate.Router but supports multiple migrations dirs.c Ztj|fd|di|||_dS)N migrate_dirr)super__init__migrations_dirs)selfdatabaser kwargs __class__s K/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/router.pyr zRouter.__init__s:LLq/ALVLLL.c0jD]P}tj|s/jd|tj|Qg}jD]2}|tfdtj|Dz }3|S)zScan migrations in file system.z'Migration directory: %s does not exist.c3|K|]6}j||dtd V7dS)N.py)filemaskmatchlen).0fr s r zRouter.todo..s^&&=&&q))&-SZZK- &&&&&&r) r ospathexistsloggerwarnmakedirssortedlistdir)r rmigration_namess` rtodoz Router.todos / ) )K7>>+.. )   ={ K(((/  K v&&&&K00&&&   OO rc i}|jD]}tt5ttj||dz5}t|ddd}t||dddn #1swxYwYdddn #1swxYwY| dt| dtfS) zRead migration from file.rzexecT) dont_inheritNmigraterollback) r rFileNotFoundErroropenrrjoincompilereadr&getr)r namescoperrcodes rr.z Router.read&sR/ & &K+,, & &"',,{D5LAABB&a"*f4Du%%% &&&&&&&&&&&&&&& & & & & & & & & & & & & & & & yyD))599Z+F+FFFs51B*6B B*B B*B B**B. 1B. ) __name__ __module__ __qualname____doc__r propertyr$r. __classcell__)rs@rrr soKK/////X" G G G G G G Gr) r6r contextlibrpeewee_migrater PeeweeRouterpeewee_migrate.routerr__all__rrr?s'' 111111&&&&&& *$G$G$G$G$G\$G$G$G$G$Grdefence360agent/__pycache__/run.cpython-311.opt-1.pyc0000644000000000000000000000043000000000000017220 0ustar r_jm dZdS))zdefence360agent.pluginsz*defence360agent.feature_management.pluginsN)CORE_PLUGINS_PACKAGESH/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/run.pyrsrdefence360agent/__pycache__/run.cpython-311.pyc0000644000000000000000000000043000000000000016261 0ustar r_jm dZdS))zdefence360agent.pluginsz*defence360agent.feature_management.pluginsN)CORE_PLUGINS_PACKAGESH/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/run.pyrsrdefence360agent/__pycache__/sentry.cpython-311.opt-1.pyc0000644000000000000000000002055300000000000017750 0ustar r_j  zdZddlZddlZddlZddlmZddlmZddlm Z m Z m Z ddl Z ddl Z ddlmZddlmZdZd Zd Zd Zd Zd ZdZedZdZdefdZdefdZde edefdZdedefdZdedefdZ dZ!dZ"defdZ#dZ$dZ% d&ded e e&d!e d"d#e ed$e ef d%Z'dS)'z2Helper for integrate sentry in stand-alone scriptsN)suppress)Path)ListOptionalLiteral)tags)sentry imunify360zimunify-antiviruszimunify360-firewallz/var/imunify360/license.jsonz!/var/imunify360/license-free.json IMUNIFYAVUNKNOWNz,/opt/imunify360/venv/share/imunify360/sentryzQhttps://6de77a2763bd40c58fc9e3a89285aaa8@im360.sentry.cloudlinux.com/3?timeout=20returnc tdS#ttf$r t cYSwxYw)z,Return dsn from the file or the default one.ascii)encoding)SENTRY_DSN_PATH read_textstripOSErrorUnicodeDecodeErrorSENTRY_DSN_DEFAULTK/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/sentry.pyget_sentry_dsnrsW"(('(::@@BBB ' ("""!!!!"s,/A  A c tt5ttfD]}tt5t |5}t j|dcdddcdddccdddS#1swxYwYdddn #1swxYwY dddn #1swxYwYtS)Nid) r ExceptionLICENSE LICENSE_FREEFileNotFoundErroropenjsonload UNKNOWN_ID)filenamefiles r get_server_idr'$s )  -- ,/ - -H+,, - -d8nn -yt, - - - - - - - - - - - - - - - --------- - - - - - - - - - - - - - - - - - - - - - - - ---------------- s^%B;B" B $ B"0B; B B"B B" B;"B& &B;)B& *B;;B?B?cmdc tj|tjtjtj}n#t$rYdSwxYw|jdkrdSt j|jS)N)stdinstdoutstderrr) subprocessrunDEVNULLPIPEr returncodeosfsdecoder+)r(cps rcollect_outputr6,s{ ^ $?%     rr }r ;ry ! !!s69 AApkgc,ddd|g}t|S)Nrpmz-qz#--queryformat=%{VERSION}-%{RELEASE}r6r7r(s rget_rpm_versionr<;s $=s CC #  rc,ddd|g}t|S)Nz dpkg-queryz--showformat=${Version}z--showr:r;s rget_dpkg_versionr>@s 2Hc BC #  rc\tjd}|S)Nr)distrolinux_distributionlower) platform_oss rget_current_osrDEs'+--a0K     rct}t}|dkrttrt}nt}|SNubuntu)rDIMUNIFY360_PKGr<r IMUNIFY360)rC service_names rget_package_namerKJs< ""K!Lh?9#=#= ! rclt}|dkrt|}nt|}|SrF)rDr<r>)rJrCversions rget_service_versionrNTs9 ""Kh!,//"<00 Nrctjttj5}t }dt i|_|d||dt|tj tj D]\}}||| ddddS#1swxYwYdS)N)dsnrnamerM) sentry_sdkinitrconfigure_scoperKr'userset_tagrNr cached_fillr items)scopepackagetagvalues rconfigure_sentryr]]s)O(())))  # % %&"$$MOO,  fg&&& i!4W!=!=>>>  +----// & &JC MM#u % % % % & &&&&&&&&&&&&&&&&&&sB1C44C8;C8cftjjj}||ddSdS)Ng@)timeout)rRHubcurrentclientflush)rbs r flush_sentryrdks7 ^ # *F  S !!!!!rwarningmessage format_argslevel)fatalcriticalerrorreinfodebug fingerprint componentc ||i}|r! |jdi|}n#t$r|}YnwxYw|}|dd|s|rdtj5}|r|g|_|r|d|tj|fd|i|ddddS#1swxYwYdStj|fd|i|dS)a Helper function to log messages to Sentry with optional fingerprinting. This is useful when you need to log messages to Sentry without relying on error handling. Args: message: The message to log format_args: Dictionary of arguments to format the message with (optional) level: Log level (default: "warning") fingerprint: String for Sentry fingerprinting (optional) component: Component name to tag the message with (optional) **kwargs: Additional keyword arguments passed to sentry_sdk.capture_message() Common options include: - extra: dict of extra data to include - tags: dict of additional tags - contexts: dict of additional contexts Nrhror)formatKeyErrorpoprR push_scopernrVcapture_message)rfrgrhrnrokwargsformatted_messagerYs r log_messagerxqs6 $ ( . = = = =   ( ( ( '    ($ JJw Mi M  " $ $  2%0M! 6 k9555  &!  ). 28                       "#4LLELVLLLLLs  %%7BB"B)NreNN)(__doc__r"r3r. contextlibrpathlibrtypingrrrr@rRdefence360agent.applicationrdefence360agent.contractsr rIr rHrrFREE_IDr$rrstrrr'r6r<r>rDrKrNr]rddictrxrrrrsW88 ********** ,,,,,,,,,,,,  & (2   $EFFh"""""s "S "c " " " " ##   & & &"""#' !%#5M5M 5M$5M @ 5M # 5M}5M5M5M5M5M5Mrdefence360agent/__pycache__/sentry.cpython-311.pyc0000644000000000000000000002055300000000000017011 0ustar r_j  zdZddlZddlZddlZddlmZddlmZddlm Z m Z m Z ddl Z ddl Z ddlmZddlmZdZd Zd Zd Zd Zd ZdZedZdZdefdZdefdZde edefdZdedefdZdedefdZ dZ!dZ"defdZ#dZ$dZ% d&ded e e&d!e d"d#e ed$e ef d%Z'dS)'z2Helper for integrate sentry in stand-alone scriptsN)suppress)Path)ListOptionalLiteral)tags)sentry imunify360zimunify-antiviruszimunify360-firewallz/var/imunify360/license.jsonz!/var/imunify360/license-free.json IMUNIFYAVUNKNOWNz,/opt/imunify360/venv/share/imunify360/sentryzQhttps://6de77a2763bd40c58fc9e3a89285aaa8@im360.sentry.cloudlinux.com/3?timeout=20returnc tdS#ttf$r t cYSwxYw)z,Return dsn from the file or the default one.ascii)encoding)SENTRY_DSN_PATH read_textstripOSErrorUnicodeDecodeErrorSENTRY_DSN_DEFAULTK/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/sentry.pyget_sentry_dsnrsW"(('(::@@BBB ' ("""!!!!"s,/A  A c tt5ttfD]}tt5t |5}t j|dcdddcdddccdddS#1swxYwYdddn #1swxYwY dddn #1swxYwYtS)Nid) r ExceptionLICENSE LICENSE_FREEFileNotFoundErroropenjsonload UNKNOWN_ID)filenamefiles r get_server_idr'$s )  -- ,/ - -H+,, - -d8nn -yt, - - - - - - - - - - - - - - - --------- - - - - - - - - - - - - - - - - - - - - - - - ---------------- s^%B;B" B $ B"0B; B B"B B" B;"B& &B;)B& *B;;B?B?cmdc tj|tjtjtj}n#t$rYdSwxYw|jdkrdSt j|jS)N)stdinstdoutstderrr) subprocessrunDEVNULLPIPEr returncodeosfsdecoder+)r(cps rcollect_outputr6,s{ ^ $?%     rr }r ;ry ! !!s69 AApkgc,ddd|g}t|S)Nrpmz-qz#--queryformat=%{VERSION}-%{RELEASE}r6r7r(s rget_rpm_versionr<;s $=s CC #  rc,ddd|g}t|S)Nz dpkg-queryz--showformat=${Version}z--showr:r;s rget_dpkg_versionr>@s 2Hc BC #  rc\tjd}|S)Nr)distrolinux_distributionlower) platform_oss rget_current_osrDEs'+--a0K     rct}t}|dkrttrt}nt}|SNubuntu)rDIMUNIFY360_PKGr<r IMUNIFY360)rC service_names rget_package_namerKJs< ""K!Lh?9#=#= ! rclt}|dkrt|}nt|}|SrF)rDr<r>)rJrCversions rget_service_versionrNTs9 ""Kh!,//"<00 Nrctjttj5}t }dt i|_|d||dt|tj tj D]\}}||| ddddS#1swxYwYdS)N)dsnrnamerM) sentry_sdkinitrconfigure_scoperKr'userset_tagrNr cached_fillr items)scopepackagetagvalues rconfigure_sentryr]]s)O(())))  # % %&"$$MOO,  fg&&& i!4W!=!=>>>  +----// & &JC MM#u % % % % & &&&&&&&&&&&&&&&&&&sB1C44C8;C8cftjjj}||ddSdS)Ng@)timeout)rRHubcurrentclientflush)rbs r flush_sentryrdks7 ^ # *F  S !!!!!rwarningmessage format_argslevel)fatalcriticalerrorreinfodebug fingerprint componentc ||i}|r! |jdi|}n#t$r|}YnwxYw|}|dd|s|rdtj5}|r|g|_|r|d|tj|fd|i|ddddS#1swxYwYdStj|fd|i|dS)a Helper function to log messages to Sentry with optional fingerprinting. This is useful when you need to log messages to Sentry without relying on error handling. Args: message: The message to log format_args: Dictionary of arguments to format the message with (optional) level: Log level (default: "warning") fingerprint: String for Sentry fingerprinting (optional) component: Component name to tag the message with (optional) **kwargs: Additional keyword arguments passed to sentry_sdk.capture_message() Common options include: - extra: dict of extra data to include - tags: dict of additional tags - contexts: dict of additional contexts Nrhror)formatKeyErrorpoprR push_scopernrVcapture_message)rfrgrhrnrokwargsformatted_messagerYs r log_messagerxqs6 $ ( . = = = =   ( ( ( '    ($ JJw Mi M  " $ $  2%0M! 6 k9555  &!  ). 28                       "#4LLELVLLLLLs  %%7BB"B)NreNN)(__doc__r"r3r. contextlibrpathlibrtypingrrrr@rRdefence360agent.applicationrdefence360agent.contractsr rIr rHrrFREE_IDr$rrstrrr'r6r<r>rDrKrNr]rddictrxrrrrsW88 ********** ,,,,,,,,,,,,  & (2   $EFFh"""""s "S "c " " " " ##   & & &"""#' !%#5M5M 5M$5M @ 5M # 5M}5M5M5M5M5M5Mrdefence360agent/_version.py0000644000000000000000000000012300000000000012740 0ustar # DO NOT EDIT: bump_version.py keeps it in sync with *.spec __version__ = "8.12.1" defence360agent/api/0000755000000000000000000000000000000000000011317 5ustar defence360agent/api/__init__.py0000644000000000000000000000000000000000000013416 0ustar defence360agent/api/__pycache__/0000755000000000000000000000000000000000000013527 5ustar defence360agent/api/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000027600000000000020734 0ustar r_jdS)NrQ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/__init__.pyrsrdefence360agent/api/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000027600000000000017775 0ustar r_jdS)NrQ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/__init__.pyrsrdefence360agent/api/__pycache__/health.cpython-311.opt-1.pyc0000644000000000000000000001142200000000000020435 0ustar r_j fdZddlZejdddgZGddZeZdS)aThis module implements health status reporting for watchdog operation. Module receive important health metrics and exports its status of overall health assessment. This health assessment can be used by external watchdog scripts to initiate agent restart. Process is considered "healthy" if: * it is being shut down and shutdown timeout has not elapsed -> HEALTHY * it is not registered -> HEALTHY * process was started more than 6 hours ago and no data was sent to server within last 6 hours -> FAULTY * process was started more than 18 hours ago and no data was received from server within last 18 hours -> FAULTY Otherwise process is considered HEALTHY. As agent exports this information through RPC interface there is an additional implicit "health" requirement that: * it responds to RPC requests. This implicit requirement considered valid because UI fully depends on RPC so it does not make health assessment any worse than it should.N HealthStatushealthywhyceZdZdZdZdZdZdZdeddfd Z deddfd Z deddfd Z deddfd Z dd Z ddZdedefdZdS) HealthSensoraHealthSensor receives events about agent operation and provides information about overall status. Initially, new HealthSensor object assumes: * process was started long ago; * process is not being shut down; * data from server has been received long ago; * data to server was sent long ago; * agent is registered (license is valid). So, initial health status is False (faulty).i i`TiXcLd|_d|_d|_d|_d|_dS)NgT) _started_at _shutdown_at_last_received _last_sent_is_registeredselfs O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/health.py__init__zHealthSensor.__init__1s/!"whenreturnNc||_dS)z!Records a moment of agent startupN)r rrs rstartingzHealthSensor.starting8src||_dS)z7Records a moment of externally initiated agent shutdownN)r rs r shutting_downzHealthSensor.shutting_down<s rc||_dS)z3Records a moment when data was received from serverN)r rs rserver_data_receivedz!HealthSensor.server_data_received@"rc||_dS)z-Records a moment when data was sent to serverN)r rs rserver_data_sentzHealthSensor.server_data_sentDs rcd|_dS)zMarks agent as being registeredTNr rs r registeredzHealthSensor.registeredHrrcd|_dS)z#Marks agent as being not registeredFNr rs r unregisteredzHealthSensor.unregisteredLs#rnowc|jdkr3||jz |jkrtddStddS|jstddS||jz |jkr#||jz |jkrtddS||jz |jkr#||jz |jkrtddStdd S) NrFzstuck at shutdownTzshutdown is in progressznot registeredzno data received from serverzno data sent to serverz all is ok) r SHUTDOWN_TIMEOUTrr r RECEIVE_WINDOWr SEND_WINDOWr )rr$s rstatuszHealthSensor.statusPs  q T&&$*???#E+>???&?@@ @" 8&677 7 $" "d&9 9 9d))T-@@@'EFF F $" "d&6 6 6do%)999'?@@ @D+...r)rN)__name__ __module__ __qualname____doc__r'r(r&rfloatrrrrr!r#rr)rrrrs 4 4NK### U t    !%!D!!!!##4####Ut####$$$$/%/L//////rr)r- collections namedtuplerrsensorr/rrr3szCC2%{%ny%6HII B/B/B/B/B/B/B/B/J rdefence360agent/api/__pycache__/health.cpython-311.pyc0000644000000000000000000001142200000000000017476 0ustar r_j fdZddlZejdddgZGddZeZdS)aThis module implements health status reporting for watchdog operation. Module receive important health metrics and exports its status of overall health assessment. This health assessment can be used by external watchdog scripts to initiate agent restart. Process is considered "healthy" if: * it is being shut down and shutdown timeout has not elapsed -> HEALTHY * it is not registered -> HEALTHY * process was started more than 6 hours ago and no data was sent to server within last 6 hours -> FAULTY * process was started more than 18 hours ago and no data was received from server within last 18 hours -> FAULTY Otherwise process is considered HEALTHY. As agent exports this information through RPC interface there is an additional implicit "health" requirement that: * it responds to RPC requests. This implicit requirement considered valid because UI fully depends on RPC so it does not make health assessment any worse than it should.N HealthStatushealthywhyceZdZdZdZdZdZdZdeddfd Z deddfd Z deddfd Z deddfd Z dd Z ddZdedefdZdS) HealthSensoraHealthSensor receives events about agent operation and provides information about overall status. Initially, new HealthSensor object assumes: * process was started long ago; * process is not being shut down; * data from server has been received long ago; * data to server was sent long ago; * agent is registered (license is valid). So, initial health status is False (faulty).i i`TiXcLd|_d|_d|_d|_d|_dS)NgT) _started_at _shutdown_at_last_received _last_sent_is_registeredselfs O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/health.py__init__zHealthSensor.__init__1s/!"whenreturnNc||_dS)z!Records a moment of agent startupN)r rrs rstartingzHealthSensor.starting8src||_dS)z7Records a moment of externally initiated agent shutdownN)r rs r shutting_downzHealthSensor.shutting_down<s rc||_dS)z3Records a moment when data was received from serverN)r rs rserver_data_receivedz!HealthSensor.server_data_received@"rc||_dS)z-Records a moment when data was sent to serverN)r rs rserver_data_sentzHealthSensor.server_data_sentDs rcd|_dS)zMarks agent as being registeredTNr rs r registeredzHealthSensor.registeredHrrcd|_dS)z#Marks agent as being not registeredFNr rs r unregisteredzHealthSensor.unregisteredLs#rnowc|jdkr3||jz |jkrtddStddS|jstddS||jz |jkr#||jz |jkrtddS||jz |jkr#||jz |jkrtddStdd S) NrFzstuck at shutdownTzshutdown is in progressznot registeredzno data received from serverzno data sent to serverz all is ok) r SHUTDOWN_TIMEOUTrr r RECEIVE_WINDOWr SEND_WINDOWr )rr$s rstatuszHealthSensor.statusPs  q T&&$*???#E+>???&?@@ @" 8&677 7 $" "d&9 9 9d))T-@@@'EFF F $" "d&6 6 6do%)999'?@@ @D+...r)rN)__name__ __module__ __qualname____doc__r'r(r&rfloatrrrrr!r#rr)rrrrs 4 4NK### U t    !%!D!!!!##4####Ut####$$$$/%/L//////rr)r- collections namedtuplerrsensorr/rrr3szCC2%{%ny%6HII B/B/B/B/B/B/B/B/J rdefence360agent/api/__pycache__/inactivity.cpython-311.opt-1.pyc0000644000000000000000000000713000000000000021354 0ustar r_jrdZddlZddlmZmZddlmZeeZGddZ e Z dS)zThis module implement inactivity tracker for ImunifyAV to automaticaly shutdown the process when it is idle for certain time (no RPC calls and long running tasks). N)contextmanagersuppress) getLoggercXeZdZdZdZedZdZdZdZ dZ de d d fd Z d S) InactivityTrackerc`tj|_d|_g|_d|_dS)Nr)time monotonic_last_action_timestamp_long_action_counter_long_actions_list_timeoutselfs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/inactivity.py__init__zInactivityTracker.__init__ s-&*n&6&6#$%!"$ cjdtj|jz |jS)Nz0Time from last action is {:.0f}, long actions {})formatr r r r rs r__str__zInactivityTracker.__str__s3AHH N  t: :  #   rc#K|| dV||dS#||wxYwN)startstoprnames rtaskzInactivityTracker.tasksM 4  EEE IIdOOOOODIIdOOOOs 4A c6tj|_dSr)r r r rs r reset_timerzInactivityTracker.reset_timer!s&*n&6&6###rc|xjdz c_|j||dSN)r r appendrrs rrzInactivityTracker.start$sE !!Q&!! &&t,,, rc|xjdzc_tt5|j|dddn #1swxYwY|dSr!)r r ValueErrorr removerrs rrzInactivityTracker.stop)s !!Q&!! j ! ! 1 1  # * *4 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 sA  AAcZ|j o#|j|jztjkSr)r r rr r rs r is_timeoutzInactivityTracker.is_timeout/s/--  '$- 74>;K;K K rtimeoutreturnNc||_dSr)r)rr)s r set_timeoutzInactivityTracker.set_timeout4s  r) __name__ __module__ __qualname__rrrrrrrr(intr,rrrr s    ^777      3 4      rr) __doc__r contextlibrrloggingrr-loggerrtrackr1rrr7s //////// 8  ) ) ) ) ) ) ) ) X rdefence360agent/api/__pycache__/inactivity.cpython-311.pyc0000644000000000000000000000713000000000000020415 0ustar r_jrdZddlZddlmZmZddlmZeeZGddZ e Z dS)zThis module implement inactivity tracker for ImunifyAV to automaticaly shutdown the process when it is idle for certain time (no RPC calls and long running tasks). N)contextmanagersuppress) getLoggercXeZdZdZdZedZdZdZdZ dZ de d d fd Z d S) InactivityTrackerc`tj|_d|_g|_d|_dS)Nr)time monotonic_last_action_timestamp_long_action_counter_long_actions_list_timeoutselfs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/inactivity.py__init__zInactivityTracker.__init__ s-&*n&6&6#$%!"$ cjdtj|jz |jS)Nz0Time from last action is {:.0f}, long actions {})formatr r r r rs r__str__zInactivityTracker.__str__s3AHH N  t: :  #   rc#K|| dV||dS#||wxYwN)startstoprnames rtaskzInactivityTracker.tasksM 4  EEE IIdOOOOODIIdOOOOs 4A c6tj|_dSr)r r r rs r reset_timerzInactivityTracker.reset_timer!s&*n&6&6###rc|xjdz c_|j||dSN)r r appendrrs rrzInactivityTracker.start$sE !!Q&!! &&t,,, rc|xjdzc_tt5|j|dddn #1swxYwY|dSr!)r r ValueErrorr removerrs rrzInactivityTracker.stop)s !!Q&!! j ! ! 1 1  # * *4 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 sA  AAcZ|j o#|j|jztjkSr)r r rr r rs r is_timeoutzInactivityTracker.is_timeout/s/--  '$- 74>;K;K K rtimeoutreturnNc||_dSr)r)rr)s r set_timeoutzInactivityTracker.set_timeout4s  r) __name__ __module__ __qualname__rrrrrrrr(intr,rrrr s    ^777      3 4      rr) __doc__r contextlibrrloggingrr-loggerrtrackr1rrr7s //////// 8  ) ) ) ) ) ) ) ) X rdefence360agent/api/__pycache__/integration_conf.cpython-311.opt-1.pyc0000644000000000000000000001436700000000000022533 0ustar r_jg|dZddlZddlmZddlmZGddZGddeZGd d eZdS) aSchema reference for `integration.conf`. Values are always returned as strings by `BaseConfig.get`. Type parsing (int, int list, bool, rule-id map) is the caller's responsibility. The format columns below are advisory conventions shared between producers (wizard, installers, panel templates) and consumers, not runtime-enforced schemas. Validation today is narrow: `other/compatibility-check.sh` validates INI syntax, `[paths] ui_path`, and the `panel_info` script at install time; integration-script JSON outputs are validated at runtime via Cerberus schemas under `panels/generic/users_script_schemas/`. All other keys are read on demand and trusted. Non-obvious `.get()` behavior: - Missing file returns `None` (ConfigParser.read silently ignores missing paths; use `BaseConfig.exists()` to distinguish). - Malformed INI propagates `configparser.Error`; `.get()` only catches `KeyError`. - Section names are case-sensitive (ConfigParser default); option names are case-insensitive. Match the casing documented below. Sections -------- `[PAM]` - `SERVICE_NAME` (str): PAM service used for UI login authentication. `[panel]` - `type` (str, `cpanel`|`plesk`|`directadmin`|`generic`): master switch for panel class selection. `[panel_ports]` Comma-separated integer lists (e.g. `2082, 2095`). Empty or absent means "no ports of this class". - `http_ports`: ports the panel listens on for HTTP admin traffic. - `https_ports`: HTTPS equivalents. - `webshield_protected_ports`: subset of the above that WebShield should protect. `[panel_login]` - `ossec_rules` (str, comma-separated `rule_id:bool` pairs, e.g. `11006:false,11009:true`): OSSEC rule IDs to auto-whitelist for panel-login events. `[features]` Boolean feature flags. Conventional values: `true` / `false` (case-insensitive). - `webshield_enabled` (bool) - `cphulk_enabled` (bool) `[smtp]` - `allow_users` (str, comma-separated list of usernames): system users allowed to send SMTP when the SMTP block feature is active. - `conflict_config_file` (str, absolute path): panel config file whose value toggles the SMTP-block conflict check. - `conflict_config_key` (str): key inside `conflict_config_file` that holds the conflicting setting. `[web_server]` - `server_type` (str, `apache`|`nginx`|...): web server in use. - `modsec_audit_log` (str, absolute path): ModSecurity audit log file. - `modsec_audit_logdir` (str, absolute path): ModSecurity audit log directory (concurrent writer layout). - `graceful_restart_script` (str, command string): whitespace-split command used to gracefully restart the web server (e.g. `/usr/bin/systemctl restart apache2`). - `config_test_script` (str, command string): whitespace-split command used to validate the web server configuration before reload (e.g. `/usr/sbin/apache2ctl -t`). `[integration_scripts]` Values are absolute paths to scripts executed by the agent as root. Populate with trusted, integrator-controlled paths only; do not interpolate user-controlled data. - `users` (str path): emits JSON user list. - `domains` (str path): emits JSON domain -> owner mapping. - `admins` (str path): emits JSON admin list. - `panel_info` (str path): emits JSON `{name, version, ...}` describing the panel. - `modsec_domain_config_script` (str path): emits per-domain ModSecurity overrides. `[paths]` - `ui_path` (str, absolute path): document root for the standalone UI. - `ui_path_owner` (str, `user:group`): owner applied to UI files during install. `[malware]` - `basedir` (str, **whitespace-separated** paths): base directories scanned for malware. Note the separator differs from port lists (whitespace here, comma for port lists). `[metadata]` - `schema_version` (int): schema version number. - `created_by` (str, `wizard`|`agent`|`manual`): who wrote the file; useful for support triage. N)Optional)GP_FILEcjeZdZedZedZedededeefdZdS) BaseConfigcJtj|jS)N)ospathexists _conf_path)clss Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/integration_conf.pyr zBaseConfig.existsqsw~~cn---cZddlm}|}||j|S)Nr) ConfigParser) configparserrreadr )r rintegration_confs r to_dictzBaseConfig.to_dictus<------'<>>cn---rsectionoptionreturncf |||S#t$rYdSwxYw)z` Return *option* value in *section* in config if exist, None otherwise. N)rKeyError)r rrs r getzBaseConfig.get~sA  ;;==)&1 1   44 s " 00N) __name__ __module__ __qualname__ classmethodr rstrrrrr rrps..[.  [ #sx}[rrceZdZeZdS)IntegrationConfigN)rrrrr r rr r"r"sJJJrr"ceZdZdZdS)ClIntegrationConfigz!/opt/cpvendor/etc/integration.iniN)rrrr r rr r$r$s4JJJrr$) __doc__rtypingr3defence360agent.application.determine_hosting_panelrrr"r$r rr r(sggR GGGGGG4 55555*55555rdefence360agent/api/__pycache__/integration_conf.cpython-311.pyc0000644000000000000000000001436700000000000021574 0ustar r_jg|dZddlZddlmZddlmZGddZGddeZGd d eZdS) aSchema reference for `integration.conf`. Values are always returned as strings by `BaseConfig.get`. Type parsing (int, int list, bool, rule-id map) is the caller's responsibility. The format columns below are advisory conventions shared between producers (wizard, installers, panel templates) and consumers, not runtime-enforced schemas. Validation today is narrow: `other/compatibility-check.sh` validates INI syntax, `[paths] ui_path`, and the `panel_info` script at install time; integration-script JSON outputs are validated at runtime via Cerberus schemas under `panels/generic/users_script_schemas/`. All other keys are read on demand and trusted. Non-obvious `.get()` behavior: - Missing file returns `None` (ConfigParser.read silently ignores missing paths; use `BaseConfig.exists()` to distinguish). - Malformed INI propagates `configparser.Error`; `.get()` only catches `KeyError`. - Section names are case-sensitive (ConfigParser default); option names are case-insensitive. Match the casing documented below. Sections -------- `[PAM]` - `SERVICE_NAME` (str): PAM service used for UI login authentication. `[panel]` - `type` (str, `cpanel`|`plesk`|`directadmin`|`generic`): master switch for panel class selection. `[panel_ports]` Comma-separated integer lists (e.g. `2082, 2095`). Empty or absent means "no ports of this class". - `http_ports`: ports the panel listens on for HTTP admin traffic. - `https_ports`: HTTPS equivalents. - `webshield_protected_ports`: subset of the above that WebShield should protect. `[panel_login]` - `ossec_rules` (str, comma-separated `rule_id:bool` pairs, e.g. `11006:false,11009:true`): OSSEC rule IDs to auto-whitelist for panel-login events. `[features]` Boolean feature flags. Conventional values: `true` / `false` (case-insensitive). - `webshield_enabled` (bool) - `cphulk_enabled` (bool) `[smtp]` - `allow_users` (str, comma-separated list of usernames): system users allowed to send SMTP when the SMTP block feature is active. - `conflict_config_file` (str, absolute path): panel config file whose value toggles the SMTP-block conflict check. - `conflict_config_key` (str): key inside `conflict_config_file` that holds the conflicting setting. `[web_server]` - `server_type` (str, `apache`|`nginx`|...): web server in use. - `modsec_audit_log` (str, absolute path): ModSecurity audit log file. - `modsec_audit_logdir` (str, absolute path): ModSecurity audit log directory (concurrent writer layout). - `graceful_restart_script` (str, command string): whitespace-split command used to gracefully restart the web server (e.g. `/usr/bin/systemctl restart apache2`). - `config_test_script` (str, command string): whitespace-split command used to validate the web server configuration before reload (e.g. `/usr/sbin/apache2ctl -t`). `[integration_scripts]` Values are absolute paths to scripts executed by the agent as root. Populate with trusted, integrator-controlled paths only; do not interpolate user-controlled data. - `users` (str path): emits JSON user list. - `domains` (str path): emits JSON domain -> owner mapping. - `admins` (str path): emits JSON admin list. - `panel_info` (str path): emits JSON `{name, version, ...}` describing the panel. - `modsec_domain_config_script` (str path): emits per-domain ModSecurity overrides. `[paths]` - `ui_path` (str, absolute path): document root for the standalone UI. - `ui_path_owner` (str, `user:group`): owner applied to UI files during install. `[malware]` - `basedir` (str, **whitespace-separated** paths): base directories scanned for malware. Note the separator differs from port lists (whitespace here, comma for port lists). `[metadata]` - `schema_version` (int): schema version number. - `created_by` (str, `wizard`|`agent`|`manual`): who wrote the file; useful for support triage. N)Optional)GP_FILEcjeZdZedZedZedededeefdZdS) BaseConfigcJtj|jS)N)ospathexists _conf_path)clss Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/integration_conf.pyr zBaseConfig.existsqsw~~cn---cZddlm}|}||j|S)Nr) ConfigParser) configparserrreadr )r rintegration_confs r to_dictzBaseConfig.to_dictus<------'<>>cn---rsectionoptionreturncf |||S#t$rYdSwxYw)z` Return *option* value in *section* in config if exist, None otherwise. N)rKeyError)r rrs r getzBaseConfig.get~sA  ;;==)&1 1   44 s " 00N) __name__ __module__ __qualname__ classmethodr rstrrrrr rrps..[.  [ #sx}[rrceZdZeZdS)IntegrationConfigN)rrrrr r rr r"r"sJJJrr"ceZdZdZdS)ClIntegrationConfigz!/opt/cpvendor/etc/integration.iniN)rrrr r rr r$r$s4JJJrr$) __doc__rtypingr3defence360agent.application.determine_hosting_panelrrr"r$r rr r(sggR GGGGGG4 55555*55555rdefence360agent/api/__pycache__/jwt_issuer.cpython-311.opt-1.pyc0000644000000000000000000001257400000000000021377 0ustar r_j ddlZddlZddlZddlmZmZddlmZddlmZddl m Z m Z ddl m Z e je je je jiZGddZdS) N)datetime timedelta)Path)InvalidTokenException)UIRoleUserType)atomic_rewritecneZdZedZedZejddZejddZ e e eZ e e e Z edZed efd Zed ed ed efd Zededed edzfdZedefdZdS) JWTIssuerz/var/imunify360/.api-secret.keyz$/var/imunify360/.api-secret-prev.key#I360_JWT_TOKEN_EXPIRATION_TTL_HOURS$I360_JWT_SECRET_EXPIRATION_TTL_HOURS)hoursc tj|j}|j}n#t$rd}YnwxYwt j|z |jj kS)Ng) osstatJWT_SECRET_FILEst_mtimeFileNotFoundErrorrnow timestampSECRET_EXPIRATION_TTLseconds)clsrrs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/jwt_issuer.pyis_secret_expiredzJWTIssuer.is_secret_expiredsy %73.//D}HH!   HHH  LNN $ $ & & 1'/ 0 s # 22returnc|rtjtjzdfdt dD}|js|jtt|j|t|j dd|S|j S)Nc3@K|]}tjVdS)N)secretschoice).0_alphabets r z(JWTIssuer._get_secret..0s- M Ma!9!9 M M M M M M@i)backupuid permissions) rstringascii_uppercasedigitsjoinrangerexiststouchr strJWT_SECRET_FILE_PREV read_text)r new_secretr&s @r _get_secretzJWTIssuer._get_secret,s  " " 3- =H M M M M599 M M MMMJ&--// ,#))+++ C'((3344!      &0022 2r( user_name user_typecddl}|||tj|jzd|S)z Generates a token with several encoded fields: user name, user type, expiration timestamp rN)r;usernameexp)jwtencoderrTOKEN_EXPIRATION_TTLrr9)rr:r;r?s r get_tokenzJWTIssuer.get_token>s_  zz&% )AALLNN   OO      r(tokensecretNc`ddl} |||dgS#|j$rYdSwxYw)NrHS256) algorithms)r?decode PyJWTError)rrCrDr?s r _parse_tokenzJWTIssuer._parse_tokenRsN  ::eV :BB B~    DD s  --c|j|jfD]_}|s|||}|r|dt |ddcS`t d)Nr=r;)r:r; INVALID_TOKEN)rr6r3rJr7UIRoleToUserTyper)rrC secret_pthdecodeds r parse_tokenzJWTIssuer.parse_token^s.0HI 9 9J$$&& &&uj.B.B.D.DEEG !(!4!1'+2F!G  (88 8r()__name__ __module__ __qualname__rrr6rgetenvJWT_TOKEN_EXPIRATION_TTL_HOURSJWT_SECRET_EXPIRATION_TTL_HOURSrintrAr classmethodrr5r9rrBdictrJrPr(rr r sd<==O4 FGG%.RY-q&&"'0bi.''#%933/M+N+NOOO%Ic122   [  3C333[3" # & S   [ &  c dTk   [  9 9 9 9[ 9 9 9r(r )rr"r.rrpathlibr"defence360agent.subsys.panels.baser defence360agent.contracts.configrrdefence360agent.utilsr ADMINROOTCLIENTNON_ROOTrMr rZr(rrcs  ((((((((DDDDDD========000000 L(- M8$ Y9Y9Y9Y9Y9Y9Y9Y9Y9Y9r(defence360agent/api/__pycache__/jwt_issuer.cpython-311.pyc0000644000000000000000000001257400000000000020440 0ustar r_j ddlZddlZddlZddlmZmZddlmZddlmZddl m Z m Z ddl m Z e je je je jiZGddZdS) N)datetime timedelta)Path)InvalidTokenException)UIRoleUserType)atomic_rewritecneZdZedZedZejddZejddZ e e eZ e e e Z edZed efd Zed ed ed efd Zededed edzfdZedefdZdS) JWTIssuerz/var/imunify360/.api-secret.keyz$/var/imunify360/.api-secret-prev.key#I360_JWT_TOKEN_EXPIRATION_TTL_HOURS$I360_JWT_SECRET_EXPIRATION_TTL_HOURS)hoursc tj|j}|j}n#t$rd}YnwxYwt j|z |jj kS)Ng) osstatJWT_SECRET_FILEst_mtimeFileNotFoundErrorrnow timestampSECRET_EXPIRATION_TTLseconds)clsrrs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/jwt_issuer.pyis_secret_expiredzJWTIssuer.is_secret_expiredsy %73.//D}HH!   HHH  LNN $ $ & & 1'/ 0 s # 22returnc|rtjtjzdfdt dD}|js|jtt|j|t|j dd|S|j S)Nc3@K|]}tjVdS)N)secretschoice).0_alphabets r z(JWTIssuer._get_secret..0s- M Ma!9!9 M M M M M M@i)backupuid permissions) rstringascii_uppercasedigitsjoinrangerexiststouchr strJWT_SECRET_FILE_PREV read_text)r new_secretr&s @r _get_secretzJWTIssuer._get_secret,s  " " 3- =H M M M M599 M M MMMJ&--// ,#))+++ C'((3344!      &0022 2r( user_name user_typecddl}|||tj|jzd|S)z Generates a token with several encoded fields: user name, user type, expiration timestamp rN)r;usernameexp)jwtencoderrTOKEN_EXPIRATION_TTLrr9)rr:r;r?s r get_tokenzJWTIssuer.get_token>s_  zz&% )AALLNN   OO      r(tokensecretNc`ddl} |||dgS#|j$rYdSwxYw)NrHS256) algorithms)r?decode PyJWTError)rrCrDr?s r _parse_tokenzJWTIssuer._parse_tokenRsN  ::eV :BB B~    DD s  --c|j|jfD]_}|s|||}|r|dt |ddcS`t d)Nr=r;)r:r; INVALID_TOKEN)rr6r3rJr7UIRoleToUserTyper)rrC secret_pthdecodeds r parse_tokenzJWTIssuer.parse_token^s.0HI 9 9J$$&& &&uj.B.B.D.DEEG !(!4!1'+2F!G  (88 8r()__name__ __module__ __qualname__rrr6rgetenvJWT_TOKEN_EXPIRATION_TTL_HOURSJWT_SECRET_EXPIRATION_TTL_HOURSrintrAr classmethodrr5r9rrBdictrJrPr(rr r sd<==O4 FGG%.RY-q&&"'0bi.''#%933/M+N+NOOO%Ic122   [  3C333[3" # & S   [ &  c dTk   [  9 9 9 9[ 9 9 9r(r )rr"r.rrpathlibr"defence360agent.subsys.panels.baser defence360agent.contracts.configrrdefence360agent.utilsr ADMINROOTCLIENTNON_ROOTrMr rZr(rrcs  ((((((((DDDDDD========000000 L(- M8$ Y9Y9Y9Y9Y9Y9Y9Y9Y9Y9r(defence360agent/api/__pycache__/newsfeed.cpython-311.opt-1.pyc0000644000000000000000000002041600000000000020773 0ustar r_j1dZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZddlmZddlmZeeZd Zd Zgd Zd d gZGdd ZdZGddZdS)z4 This module gets and caches news from imunify blog N) HTTPError) ElementTree)suppress) getLogger) HostingPanel)retry_onz!https://blog.imunify360.com/feed/i,)titlepubDateguidlinkrNewsFeedceZdZdZdZeeeje j j fdddZ edZ edZeefd Zed Zd S) r <z"/var/imunify360/tmp/feed_cache.rss ctj|SN)r clear_cache)argss Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/newsfeed.pyzNewsFeed."sx3T:) max_trieson_errorcK|r|d{Vttjt |j5}tj| }| d}fd|DcdddS#1swxYwYdS)NitemcRg|]#}|d|D$S)cDi|]}|jtv|j|jS)tag TAGS_TO_READtext.0childs r z+NewsFeed.get...-s5yL00Iuz000r) is_allowed)r#r category_infos r z NewsFeed.get..,sX  ++D11!%r) _expired_refresh PanelCategoryrNAMEopencache_file_pathr fromstringreaditer)cls cache_fileroot imunify_newsr's @rgetz NewsFeed.gets <<>> !,,.. %lnn&9:: #% & & *)*//*;*;<>#- . . #!#!1!1#2E!F!F  !" Y[[#55; 3=((rcxKtjdtt|d{VSr)asyncioget_event_looprun_in_executor _fetch_urlRSS_FEED_REMOTE_URL)r2timeouts rrAzNewsFeed._fetchIsO+--== *17         rcKtd|tt5t j|jddddS#1swxYwYdS)NzClearing cache due to error: %s)r>warningrFileNotFoundErrorr9unlinkr.)r2rs rrzNewsFeed.clear_cacheOs8$??? ' ( ( + + Ic) * * * + + + + + + + + + + + + + + + + + +sAA AN)__name__ __module__ __qualname__rFr. classmethodrr ParseErrorurllibrequestURLErrorr6r*r)_TIMEOUTrArrrrr r sI:O X  !89::  [ $11[1))[)"*   [ ++[+++rc  ddi}tj||}tj||5}|cdddS#1swxYwYdS#t j$rtwxYw)Nz User-Agentzimunify360-urllib/0.1)headers)rO)rYrZRequesturlopenr0socketrO TimeoutError)urlrOr^reqresponses rrMrMVs  !89n$$S'$:: ^ # #C # 9 9 #X==?? # # # # # # # # # # # # # # # # # # >s0AA6A) A6)A--A60A-1A66B c&eZdZhdZdZdZdZdS)r+>pleskcpanel directadminzstandalone-imunifyc|}|tjvr|n tj|_tjtjh|jhz z|_dSr)lowerr+panel_categoriesno_panel_categorycurrent competitors)selfp_names r__init__zPanelCategory.__init__isb777 F0 )9  += \N=rcd|D}d||jv}tfd|jD}| p|S)Nc2h|]}|jdk |jS)category)rr!r"s r z+PanelCategory.is_allowed..us-    EI,C,CEJ,C,C,Crz|||c3 K|]}|vV dSrr)r#comjoined_categorys r z+PanelCategory.is_allowed..}s9& & '*C? "& & & & & & r)joinrkrnanyro)rpritem_categoriescurrent_in_categorycompetitors_in_categoryrys @rr&zPanelCategory.is_allowedts  $(     **_55;;=="lo="%& & & & .2.>& & & # #  +*A.AArN)rTrUrVrlrmrrr&rrrr+r+csK:99,   BBBBBrr+)__doc__rJr9rarEurllib.requestrY urllib.errorr xml.etreer contextlibrloggingr(defence360agent.simple_rpc.hosting_panelrdefence360agent.utilsr__file__r>rNr\r __all__r rMr+rrrrsP """"""!!!!!!AAAAAA****** 8  9 333  #9+9+9+9+9+9+9+9+x   BBBBBBBBBBrdefence360agent/api/__pycache__/newsfeed.cpython-311.pyc0000644000000000000000000002041600000000000020034 0ustar r_j1dZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZddlmZddlmZeeZd Zd Zgd Zd d gZGdd ZdZGddZdS)z4 This module gets and caches news from imunify blog N) HTTPError) ElementTree)suppress) getLogger) HostingPanel)retry_onz!https://blog.imunify360.com/feed/i,)titlepubDateguidlinkrNewsFeedceZdZdZdZeeeje j j fdddZ edZ edZeefd Zed Zd S) r <z"/var/imunify360/tmp/feed_cache.rss ctj|SN)r clear_cache)argss Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/newsfeed.pyzNewsFeed."sx3T:) max_trieson_errorcK|r|d{Vttjt |j5}tj| }| d}fd|DcdddS#1swxYwYdS)NitemcRg|]#}|d|D$S)cDi|]}|jtv|j|jS)tag TAGS_TO_READtext.0childs r z+NewsFeed.get...-s5yL00Iuz000r) is_allowed)r#r category_infos r z NewsFeed.get..,sX  ++D11!%r) _expired_refresh PanelCategoryrNAMEopencache_file_pathr fromstringreaditer)cls cache_fileroot imunify_newsr's @rgetz NewsFeed.gets <<>> !,,.. %lnn&9:: #% & & *)*//*;*;<>#- . . #!#!1!1#2E!F!F  !" Y[[#55; 3=((rcxKtjdtt|d{VSr)asyncioget_event_looprun_in_executor _fetch_urlRSS_FEED_REMOTE_URL)r2timeouts rrAzNewsFeed._fetchIsO+--== *17         rcKtd|tt5t j|jddddS#1swxYwYdS)NzClearing cache due to error: %s)r>warningrFileNotFoundErrorr9unlinkr.)r2rs rrzNewsFeed.clear_cacheOs8$??? ' ( ( + + Ic) * * * + + + + + + + + + + + + + + + + + +sAA AN)__name__ __module__ __qualname__rFr. classmethodrr ParseErrorurllibrequestURLErrorr6r*r)_TIMEOUTrArrrrr r sI:O X  !89::  [ $11[1))[)"*   [ ++[+++rc  ddi}tj||}tj||5}|cdddS#1swxYwYdS#t j$rtwxYw)Nz User-Agentzimunify360-urllib/0.1)headers)rO)rYrZRequesturlopenr0socketrO TimeoutError)urlrOr^reqresponses rrMrMVs  !89n$$S'$:: ^ # #C # 9 9 #X==?? # # # # # # # # # # # # # # # # # # >s0AA6A) A6)A--A60A-1A66B c&eZdZhdZdZdZdZdS)r+>pleskcpanel directadminzstandalone-imunifyc|}|tjvr|n tj|_tjtjh|jhz z|_dSr)lowerr+panel_categoriesno_panel_categorycurrent competitors)selfp_names r__init__zPanelCategory.__init__isb777 F0 )9  += \N=rcd|D}d||jv}tfd|jD}| p|S)Nc2h|]}|jdk |jS)category)rr!r"s r z+PanelCategory.is_allowed..us-    EI,C,CEJ,C,C,Crz|||c3 K|]}|vV dSrr)r#comjoined_categorys r z+PanelCategory.is_allowed..}s9& & '*C? "& & & & & & r)joinrkrnanyro)rpritem_categoriescurrent_in_categorycompetitors_in_categoryrys @rr&zPanelCategory.is_allowedts  $(     **_55;;=="lo="%& & & & .2.>& & & # #  +*A.AArN)rTrUrVrlrmrrr&rrrr+r+csK:99,   BBBBBrr+)__doc__rJr9rarEurllib.requestrY urllib.errorr xml.etreer contextlibrloggingr(defence360agent.simple_rpc.hosting_panelrdefence360agent.utilsr__file__r>rNr\r __all__r rMr+rrrrsP """"""!!!!!!AAAAAA****** 8  9 333  #9+9+9+9+9+9+9+9+x   BBBBBBBBBBrdefence360agent/api/__pycache__/pam_auth.cpython-311.opt-1.pyc0000644000000000000000000000340100000000000020764 0ustar r_j DddlmZddlmZddlmZGddZdS))IntegrationConfig)UIRole)get_admin_listc.eZdZdZdefdZdedefdZdS)PamAuthz system-authreturncddlm} t}|dd}n#t$r |j}YnwxYw|}||||S)Nr)pamPAM SERVICE_NAME)service)r rto_dictKeyErrorDEFAULT_AUTH_SERVICE authenticate)selfusernamepasswordr configr ps Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/pam_auth.pyrzPamAuth.authenticate s 0&((0022FUmN3GG 0 0 0/GGG 0 CEE~~h'~BBBs.7A  A rcfKtd{V}||vr tjn tjS)N)rrADMINCLIENT)rradminss r get_user_typezPamAuth.get_user_types;%'''''''''611v||v}DN) __name__ __module__ __qualname__rboolrstrrrrrrrsd( C$ C C C CECEFEEEEEErrN)$defence360agent.api.integration_confr defence360agent.contracts.configr+defence360agent.subsys.panels.generic.panelrrr#rrr'swBBBBBB333333FFFFFFEEEEEEEEEErdefence360agent/api/__pycache__/pam_auth.cpython-311.pyc0000644000000000000000000000340100000000000020025 0ustar r_j DddlmZddlmZddlmZGddZdS))IntegrationConfig)UIRole)get_admin_listc.eZdZdZdefdZdedefdZdS)PamAuthz system-authreturncddlm} t}|dd}n#t$r |j}YnwxYw|}||||S)Nr)pamPAM SERVICE_NAME)service)r rto_dictKeyErrorDEFAULT_AUTH_SERVICE authenticate)selfusernamepasswordr configr ps Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/pam_auth.pyrzPamAuth.authenticate s 0&((0022FUmN3GG 0 0 0/GGG 0 CEE~~h'~BBBs.7A  A rcfKtd{V}||vr tjn tjS)N)rrADMINCLIENT)rradminss r get_user_typezPamAuth.get_user_types;%'''''''''611v||v}DN) __name__ __module__ __qualname__rboolrstrrrrrrrsd( C$ C C C CECEFEEEEEErrN)$defence360agent.api.integration_confr defence360agent.contracts.configr+defence360agent.subsys.panels.generic.panelrrr#rrr'swBBBBBB333333FFFFFFEEEEEEEEEErdefence360agent/api/health.py0000644000000000000000000000660300000000000013143 0ustar """This module implements health status reporting for watchdog operation. Module receive important health metrics and exports its status of overall health assessment. This health assessment can be used by external watchdog scripts to initiate agent restart. Process is considered "healthy" if: * it is being shut down and shutdown timeout has not elapsed -> HEALTHY * it is not registered -> HEALTHY * process was started more than 6 hours ago and no data was sent to server within last 6 hours -> FAULTY * process was started more than 18 hours ago and no data was received from server within last 18 hours -> FAULTY Otherwise process is considered HEALTHY. As agent exports this information through RPC interface there is an additional implicit "health" requirement that: * it responds to RPC requests. This implicit requirement considered valid because UI fully depends on RPC so it does not make health assessment any worse than it should.""" import collections HealthStatus = collections.namedtuple("HealthStatus", ["healthy", "why"]) class HealthSensor: """HealthSensor receives events about agent operation and provides information about overall status. Initially, new HealthSensor object assumes: * process was started long ago; * process is not being shut down; * data from server has been received long ago; * data to server was sent long ago; * agent is registered (license is valid). So, initial health status is False (faulty).""" RECEIVE_WINDOW = 18 * 3600 SEND_WINDOW = 6 * 3600 SHUTDOWN_TIMEOUT = 600 def __init__(self): self._started_at = 0.0 self._shutdown_at = 0.0 self._last_received = 0.0 self._last_sent = 0.0 self._is_registered = True def starting(self, when: float) -> None: """Records a moment of agent startup""" self._started_at = when def shutting_down(self, when: float) -> None: """Records a moment of externally initiated agent shutdown""" self._shutdown_at = when def server_data_received(self, when: float) -> None: """Records a moment when data was received from server""" self._last_received = when def server_data_sent(self, when: float) -> None: """Records a moment when data was sent to server""" self._last_sent = when def registered(self) -> None: """Marks agent as being registered""" self._is_registered = True def unregistered(self) -> None: """Marks agent as being not registered""" self._is_registered = False def status(self, now: float) -> HealthStatus: if self._shutdown_at > 0: if now - self._shutdown_at >= self.SHUTDOWN_TIMEOUT: return HealthStatus(False, "stuck at shutdown") return HealthStatus(True, "shutdown is in progress") if not self._is_registered: return HealthStatus(True, "not registered") if ( now - self._started_at >= self.RECEIVE_WINDOW and now - self._last_received >= self.RECEIVE_WINDOW ): return HealthStatus(False, "no data received from server") if ( now - self._started_at >= self.SEND_WINDOW and now - self._last_sent >= self.SEND_WINDOW ): return HealthStatus(False, "no data sent to server") return HealthStatus(True, "all is ok") sensor = HealthSensor() defence360agent/api/inactivity.py0000644000000000000000000000277000000000000014062 0ustar """This module implement inactivity tracker for ImunifyAV to automaticaly shutdown the process when it is idle for certain time (no RPC calls and long running tasks). """ import time from contextlib import contextmanager, suppress from logging import getLogger logger = getLogger(__name__) class InactivityTracker: def __init__(self): self._last_action_timestamp = time.monotonic() self._long_action_counter = 0 self._long_actions_list = [] self._timeout = 0 def __str__(self): return "Time from last action is {:.0f}, long actions {}".format( time.monotonic() - self._last_action_timestamp, self._long_actions_list, ) @contextmanager def task(self, name): self.start(name) try: yield finally: self.stop(name) def reset_timer(self): self._last_action_timestamp = time.monotonic() def start(self, name): self._long_action_counter += 1 self._long_actions_list.append(name) self.reset_timer() def stop(self, name): self._long_action_counter -= 1 with suppress(ValueError): self._long_actions_list.remove(name) self.reset_timer() def is_timeout(self): return (not self._long_action_counter) and ( self._last_action_timestamp + self._timeout <= time.monotonic() ) def set_timeout(self, timeout: int) -> None: self._timeout = timeout track = InactivityTracker() defence360agent/api/integration_conf.py0000644000000000000000000001154700000000000015231 0ustar """Schema reference for `integration.conf`. Values are always returned as strings by `BaseConfig.get`. Type parsing (int, int list, bool, rule-id map) is the caller's responsibility. The format columns below are advisory conventions shared between producers (wizard, installers, panel templates) and consumers, not runtime-enforced schemas. Validation today is narrow: `other/compatibility-check.sh` validates INI syntax, `[paths] ui_path`, and the `panel_info` script at install time; integration-script JSON outputs are validated at runtime via Cerberus schemas under `panels/generic/users_script_schemas/`. All other keys are read on demand and trusted. Non-obvious `.get()` behavior: - Missing file returns `None` (ConfigParser.read silently ignores missing paths; use `BaseConfig.exists()` to distinguish). - Malformed INI propagates `configparser.Error`; `.get()` only catches `KeyError`. - Section names are case-sensitive (ConfigParser default); option names are case-insensitive. Match the casing documented below. Sections -------- `[PAM]` - `SERVICE_NAME` (str): PAM service used for UI login authentication. `[panel]` - `type` (str, `cpanel`|`plesk`|`directadmin`|`generic`): master switch for panel class selection. `[panel_ports]` Comma-separated integer lists (e.g. `2082, 2095`). Empty or absent means "no ports of this class". - `http_ports`: ports the panel listens on for HTTP admin traffic. - `https_ports`: HTTPS equivalents. - `webshield_protected_ports`: subset of the above that WebShield should protect. `[panel_login]` - `ossec_rules` (str, comma-separated `rule_id:bool` pairs, e.g. `11006:false,11009:true`): OSSEC rule IDs to auto-whitelist for panel-login events. `[features]` Boolean feature flags. Conventional values: `true` / `false` (case-insensitive). - `webshield_enabled` (bool) - `cphulk_enabled` (bool) `[smtp]` - `allow_users` (str, comma-separated list of usernames): system users allowed to send SMTP when the SMTP block feature is active. - `conflict_config_file` (str, absolute path): panel config file whose value toggles the SMTP-block conflict check. - `conflict_config_key` (str): key inside `conflict_config_file` that holds the conflicting setting. `[web_server]` - `server_type` (str, `apache`|`nginx`|...): web server in use. - `modsec_audit_log` (str, absolute path): ModSecurity audit log file. - `modsec_audit_logdir` (str, absolute path): ModSecurity audit log directory (concurrent writer layout). - `graceful_restart_script` (str, command string): whitespace-split command used to gracefully restart the web server (e.g. `/usr/bin/systemctl restart apache2`). - `config_test_script` (str, command string): whitespace-split command used to validate the web server configuration before reload (e.g. `/usr/sbin/apache2ctl -t`). `[integration_scripts]` Values are absolute paths to scripts executed by the agent as root. Populate with trusted, integrator-controlled paths only; do not interpolate user-controlled data. - `users` (str path): emits JSON user list. - `domains` (str path): emits JSON domain -> owner mapping. - `admins` (str path): emits JSON admin list. - `panel_info` (str path): emits JSON `{name, version, ...}` describing the panel. - `modsec_domain_config_script` (str path): emits per-domain ModSecurity overrides. `[paths]` - `ui_path` (str, absolute path): document root for the standalone UI. - `ui_path_owner` (str, `user:group`): owner applied to UI files during install. `[malware]` - `basedir` (str, **whitespace-separated** paths): base directories scanned for malware. Note the separator differs from port lists (whitespace here, comma for port lists). `[metadata]` - `schema_version` (int): schema version number. - `created_by` (str, `wizard`|`agent`|`manual`): who wrote the file; useful for support triage. """ import os from typing import Optional from defence360agent.application.determine_hosting_panel import GP_FILE class BaseConfig: @classmethod def exists(cls): return os.path.exists(cls._conf_path) @classmethod def to_dict(cls): from configparser import ConfigParser integration_conf = ConfigParser() integration_conf.read(cls._conf_path) return integration_conf @classmethod def get(cls, section: str, option: str) -> Optional[str]: """ Return *option* value in *section* in config if exist, None otherwise. """ try: return cls.to_dict()[section][option] except KeyError: return None class IntegrationConfig(BaseConfig): _conf_path = GP_FILE class ClIntegrationConfig(BaseConfig): _conf_path = "/opt/cpvendor/etc/integration.ini" defence360agent/api/jwt_issuer.py0000644000000000000000000000637300000000000014100 0ustar import os import secrets import string from datetime import datetime, timedelta from pathlib import Path from defence360agent.subsys.panels.base import InvalidTokenException from defence360agent.contracts.config import UIRole, UserType from defence360agent.utils import atomic_rewrite UIRoleToUserType = { UIRole.ADMIN: UserType.ROOT, UIRole.CLIENT: UserType.NON_ROOT, } class JWTIssuer: JWT_SECRET_FILE = Path("/var/imunify360/.api-secret.key") JWT_SECRET_FILE_PREV = Path("/var/imunify360/.api-secret-prev.key") JWT_TOKEN_EXPIRATION_TTL_HOURS = os.getenv( "I360_JWT_TOKEN_EXPIRATION_TTL_HOURS", 6 ) JWT_SECRET_EXPIRATION_TTL_HOURS = os.getenv( "I360_JWT_SECRET_EXPIRATION_TTL_HOURS", 24 ) TOKEN_EXPIRATION_TTL = timedelta(hours=int(JWT_TOKEN_EXPIRATION_TTL_HOURS)) SECRET_EXPIRATION_TTL = timedelta( hours=int(JWT_SECRET_EXPIRATION_TTL_HOURS) ) @classmethod def is_secret_expired(cls): try: stat = os.stat(cls.JWT_SECRET_FILE) except FileNotFoundError: st_mtime = 0.0 else: st_mtime = stat.st_mtime return ( datetime.now().timestamp() - st_mtime > cls.SECRET_EXPIRATION_TTL.seconds ) @classmethod def _get_secret(cls) -> str: if cls.is_secret_expired(): alphabet = string.ascii_uppercase + string.digits new_secret = "".join(secrets.choice(alphabet) for _ in range(64)) if not cls.JWT_SECRET_FILE.exists(): cls.JWT_SECRET_FILE.touch() atomic_rewrite( str(cls.JWT_SECRET_FILE), new_secret, backup=str(cls.JWT_SECRET_FILE_PREV), uid=-1, permissions=0o600, ) return new_secret else: return cls.JWT_SECRET_FILE.read_text() @classmethod def get_token(cls, user_name: str, user_type: UIRole) -> str: """ Generates a token with several encoded fields: user name, user type, expiration timestamp """ import jwt return jwt.encode( { "user_type": user_type, "username": user_name, "exp": (datetime.now() + cls.TOKEN_EXPIRATION_TTL).timestamp(), }, cls._get_secret(), ) @classmethod def _parse_token(cls, token: str, secret: str) -> dict | None: import jwt # if handle these exceptions at global level, # jwt shoud be imported there, # increasing memory consumation try: return jwt.decode(token, secret, algorithms=["HS256"]) except jwt.PyJWTError: pass @classmethod def parse_token(cls, token: str): for secret_pth in [cls.JWT_SECRET_FILE, cls.JWT_SECRET_FILE_PREV]: if not secret_pth.exists(): continue decoded = cls._parse_token(token, secret_pth.read_text()) if decoded: return { "user_name": decoded["username"], "user_type": UIRoleToUserType[decoded["user_type"]], } else: raise InvalidTokenException("INVALID_TOKEN") defence360agent/api/newsfeed.py0000644000000000000000000001046100000000000013473 0ustar """ This module gets and caches news from imunify blog """ import asyncio import os import socket import time import urllib.request from urllib.error import HTTPError from xml.etree import ElementTree from contextlib import suppress from logging import getLogger from defence360agent.simple_rpc.hosting_panel import HostingPanel from defence360agent.utils import retry_on logger = getLogger(__file__) RSS_FEED_REMOTE_URL = "https://blog.imunify360.com/feed/" _TIMEOUT = 300 # default timeout for network operations here TAGS_TO_READ = ["title", "pubDate", "guid", "link"] __all__ = ["HTTPError", "NewsFeed"] class NewsFeed: cache_ttl = 60 # in minutes cache_file_path = "/var/imunify360/tmp/feed_cache.rss" @classmethod @retry_on( (ElementTree.ParseError, urllib.request.URLError), max_tries=10, on_error=lambda *args: NewsFeed.clear_cache(*args), ) async def get(cls): if cls._expired(): await cls._refresh() category_info = PanelCategory(HostingPanel().NAME) with open(cls.cache_file_path) as cache_file: root = ElementTree.fromstring(cache_file.read()) imunify_news = root.iter("item") return [ { child.tag: child.text for child in item if child.tag in TAGS_TO_READ } for item in imunify_news if category_info.is_allowed(item) ] @classmethod async def _refresh(cls): cache_file_dir_path = os.path.dirname(cls.cache_file_path) if not os.path.exists(cache_file_dir_path): os.makedirs(cache_file_dir_path) logger.info("Refresh news cache") with open(cls.cache_file_path, "wb") as cache_file: cache_file.write(await cls._fetch()) @classmethod def _expired(cls): if os.path.exists(cls.cache_file_path): last_modified_time = os.path.getmtime(cls.cache_file_path) else: last_modified_time = 0 cache_age = (time.time() - last_modified_time) / 60 # in minutes return cache_age > cls.cache_ttl @classmethod async def _fetch(cls, timeout=_TIMEOUT): return await asyncio.get_event_loop().run_in_executor( None, _fetch_url, RSS_FEED_REMOTE_URL, timeout ) @classmethod async def clear_cache(cls, *args): logger.warning("Clearing cache due to error: %s", args) with suppress(FileNotFoundError): os.unlink(cls.cache_file_path) def _fetch_url(url, timeout): try: # Cloudflare Browser Integrity Check blocks the default urllib # User-Agent. RSS feed URL was added to exceptions but they are # not free, so let's set a custom User-Agent anyway. headers = {"User-Agent": "imunify360-urllib/0.1"} req = urllib.request.Request(url, headers=headers) with urllib.request.urlopen(req, timeout=timeout) as response: return response.read() except socket.timeout: raise TimeoutError class PanelCategory: # RSS news categories, value saved in xml category tag # categories are case-insensitive so lowercase it panel_categories = {"cpanel", "plesk", "directadmin"} no_panel_category = "standalone-imunify" def __init__(self, p_name): p_name = p_name.lower() self.current = ( p_name if p_name in PanelCategory.panel_categories else PanelCategory.no_panel_category ) self.competitors = PanelCategory.panel_categories | { PanelCategory.no_panel_category } - {self.current} def is_allowed(self, item): item_categories = { child.text for child in item if child.tag == "category" } # category tag can include not only exact panel name, but also some # phrase for SEO purpose, so check it by `in` on joined string joined_category = "|||".join(item_categories).lower() current_in_category = self.current in joined_category competitors_in_category = any( com in joined_category for com in self.competitors ) # current panel didn't mentioned in categories, # but competitor was -> don't add to result return not competitors_in_category or current_in_category defence360agent/api/pam_auth.py0000644000000000000000000000141300000000000013466 0ustar from defence360agent.api.integration_conf import IntegrationConfig from defence360agent.contracts.config import UIRole from defence360agent.subsys.panels.generic.panel import get_admin_list class PamAuth: DEFAULT_AUTH_SERVICE = "system-auth" def authenticate(self, username, password) -> bool: from pam import pam try: config = IntegrationConfig().to_dict() service = config["PAM"]["SERVICE_NAME"] except KeyError: service = self.DEFAULT_AUTH_SERVICE p = pam() return p.authenticate(username, password, service=service) async def get_user_type(self, username: str) -> UIRole: admins = await get_admin_list() return UIRole.ADMIN if username in admins else UIRole.CLIENT defence360agent/api/server/0000755000000000000000000000000000000000000012625 5ustar defence360agent/api/server/__init__.py0000644000000000000000000000570200000000000014742 0ustar import asyncio import http.client import json import logging import socket import urllib.error import urllib.request from defence360agent.contracts.config import Core logger = logging.getLogger(__name__) class APIError(Exception): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) if len(args) >= 2: _, status_code, *args = args self.status_code = status_code else: self.status_code = None class APIErrorTooManyRequests(APIError): ... class APITokenError(APIError): ... class FGWSendMessgeException(Exception): ... class NATSSendMessageException(Exception): def __init__(self, *args, published=0): super().__init__(*args) self.published = published class API: _BASE_URL = Core.API_BASE_URL # socket timeout is for blocking operations # it should be as less as possible, but for sync api (remote_iplist) # we may wait for response for 25 seconds, let's set it to 45 from our side _SOCKET_TIMEOUT = 45 @classmethod def request(cls, request: urllib.request.Request, json_loads=True): try: with urllib.request.urlopen( request, # agent should be able to wait for a while # in lb queue before being connected timeout=cls._SOCKET_TIMEOUT, ) as response: logger.info( "Performed request for url=%s method=%s body size=%s" " status=%s", request.full_url, getattr(request, "method", None), len(request.data) if request.data else 0, response.status, ) if response.status != 200: raise APIError( "status code is {}".format(response.status), response.status, ) plain_response = response.read() logger.info("Response=%s ...", plain_response[:50]) if json_loads: result = json.loads(plain_response.decode()) else: result = plain_response return result except ( UnicodeDecodeError, http.client.HTTPException, json.JSONDecodeError, socket.timeout, urllib.error.URLError, ) as e: status_code = getattr(e, "code", None) if status_code == 429: raise APIErrorTooManyRequests( "request failed, reason: %s" % (e,), status_code ) from e raise APIError( "request failed, reason: %s" % (e,), status_code ) from e @classmethod async def async_request(cls, request, executor=None): loop = asyncio.get_event_loop() return await loop.run_in_executor(executor, cls.request, request) defence360agent/api/server/__pycache__/0000755000000000000000000000000000000000000015035 5ustar defence360agent/api/server/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000001224000000000000022234 0ustar r_j ddlZddlZddlZddlZddlZddlZddlZddl m Z ej e Z GddeZGddeZGddeZGd d eZGd d eZGd dZdS)N)Corec eZdZdfd ZxZS)APIErrorreturnNctj|i|t|dkr|^}}}||_dSd|_dS)N)super__init__len status_code)selfargskwargs_r __class__s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/__init__.pyr zAPIError.__init__sV$)&))) t99>>$( !A{T*D   #D   )rN__name__ __module__ __qualname__r __classcell__rs@rrrs=$$$$$$$$$$rrceZdZdS)APIErrorTooManyRequestsNrrrrrrrCrrceZdZdS) APITokenErrorNrrrrr r rrr ceZdZdS)FGWSendMessgeExceptionNrrrrr"r" rrr"c$eZdZddfd ZxZS)NATSSendMessageExceptionr) publishedcBtj|||_dSN)r r r%)r r%rrs rr z!NATSSendMessageException.__init__%s"$"rrrs@rr$r$$sE()###########rr$cjeZdZejZdZeddej j fdZ eddZ dS) API-Trequestc p tj||j5}td|jt|dd|jrt|jnd|j |j dkr-td |j |j | }td|dd|r'tj|}n|}|cdddS#1swxYwYdS#t"t$jjtjt,jtjjf$rD}t|d d}|d krt5d |||td |||d}~wwxYw) N)timeoutz=Performed request for url=%s method=%s body size=%s status=%smethodrzstatus code is {}zResponse=%s ...2codeizrequest failed, reason: )urllibr+urlopen_SOCKET_TIMEOUTloggerinfofull_urlgetattrdatar statusrformatreadjsonloadsdecodeUnicodeDecodeErrorhttpclient HTTPExceptionJSONDecodeErrorsocketr-errorURLErrorr)clsr+ json_loadsresponseplain_responseresulter s rr+z API.request1s) ''+ (   !$GXt44)0r`s  111111  8 $ $$$$$$y$$$hHY#####y### 7J7J7J7J7J7J7J7J7J7Jrdefence360agent/api/server/__pycache__/__init__.cpython-311.pyc0000644000000000000000000001224000000000000021275 0ustar r_j ddlZddlZddlZddlZddlZddlZddlZddl m Z ej e Z GddeZGddeZGddeZGd d eZGd d eZGd dZdS)N)Corec eZdZdfd ZxZS)APIErrorreturnNctj|i|t|dkr|^}}}||_dSd|_dS)N)super__init__len status_code)selfargskwargs_r __class__s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/__init__.pyr zAPIError.__init__sV$)&))) t99>>$( !A{T*D   #D   )rN__name__ __module__ __qualname__r __classcell__rs@rrrs=$$$$$$$$$$rrceZdZdS)APIErrorTooManyRequestsNrrrrrrrCrrceZdZdS) APITokenErrorNrrrrr r rrr ceZdZdS)FGWSendMessgeExceptionNrrrrr"r" rrr"c$eZdZddfd ZxZS)NATSSendMessageExceptionr) publishedcBtj|||_dSN)r r r%)r r%rrs rr z!NATSSendMessageException.__init__%s"$"rrrs@rr$r$$sE()###########rr$cjeZdZejZdZeddej j fdZ eddZ dS) API-Trequestc p tj||j5}td|jt|dd|jrt|jnd|j |j dkr-td |j |j | }td|dd|r'tj|}n|}|cdddS#1swxYwYdS#t"t$jjtjt,jtjjf$rD}t|d d}|d krt5d |||td |||d}~wwxYw) N)timeoutz=Performed request for url=%s method=%s body size=%s status=%smethodrzstatus code is {}zResponse=%s ...2codeizrequest failed, reason: )urllibr+urlopen_SOCKET_TIMEOUTloggerinfofull_urlgetattrdatar statusrformatreadjsonloadsdecodeUnicodeDecodeErrorhttpclient HTTPExceptionJSONDecodeErrorsocketr-errorURLErrorr)clsr+ json_loadsresponseplain_responseresulter s rr+z API.request1s) ''+ (   !$GXt44)0r`s  111111  8 $ $$$$$$y$$$hHY#####y### 7J7J7J7J7J7J7J7J7J7Jrdefence360agent/api/server/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000002624600000000000023672 0ustar r_jddlZddlZddlZddlZddlZddlmZmZddl m Z ddl m Z ddl mZmZddlmZddlmZejeZed Zd Zd ZGd d e ZdS)N)datetime timedelta)API)ANTIVIRUS_MODE)IndependentAgentIDAPIIAIDTokenError)run_in_executor_decorator) parse_params )minutesno_agent_tokencP tj|}nK#tttjjf$r'}td|icYd}~Sd}~wwxYwt|tr|Stdt|j iS)Nz#Cannot decode API response body: %sz&API response body is %s, not an object) jsonload ValueErrorOSErrorhttpclient HTTPExceptionloggerwarning isinstancedicttype__name__)responsebodyes _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/analyst_cleanup.py _json_objectr sy"" !: ;)H)J)J#J#J#J#J#J#JK-G--g66666666F#)CJx &.lnnCJ{ #M    55 s AC%% C32C3c ||}|ddS#t$r&}td|Yd}~dSd}~wwxYw)&Execute the actual request in executorr#Fz&Failed to check cleanup permission: %sNT)r1get Exceptionrerrorr8r1r#rs rr7z AnalystCleanupAPI._check_allowedQso [[))F::h.. .    LLA1 E E E 44444  s*- AAAidsreturncKt|tr dd|D}nt|}|j|j}d|i} tj t||dtj d{Vid}n0#t$r#}td |d}~wwxYw||d{VS) a Retrieve tickets from Zendesk API using the show_many endpoint Args: ids (list or str): List of ticket IDs or comma-separated string of IDs Returns: list: List of dictionaries with 'id', 'status', and 'updated_at' fields ,c34K|]}t|VdS)N)str).0_ids r z0AnalystCleanupAPI.get_tickets..ms(77Cs3xx777777r&rAr(Nr)r*z&Failed to get IAID token for tickets: )rlistjoinrFSHOW_MANY_URL_TEMPLATEr4r5r0r1r2r rr6rrr?_execute_get_tickets)r8rAids_strurlparamsr1rs r get_ticketszAnalystCleanupAPI.get_tickets`s, c4  hh77377777GG#hhG(//S]/CC! n,,VS))!)>)H)J)J#J#J#J#J#J#JK-GG     LLE!EE F F F  --g666666666s,A B66 C#CC#cg} ||}|dgD]T}||d|d|ddU||cS#t$r'}td|Yd}~nd}~wwxYw |S#|ccYSxYw)z2Execute the actual get_tickets request in executorticketsidstatus updated_at)rUrVrWzFailed to get tickets: N)r1r=appendr>rr?)r8r1simplified_ticketsr#ticketrs rrNz&AnalystCleanupAPI._execute_get_ticketss  &[[))F!**Y33  "))$jj.."(**X"6"6&,jj&>&>&& % % % 8 8 8 LL6166 7 7 7 7 7 7 7 7 8 7% %% % % % % % % % %s*BB B:B50C5B::CCcK tj|j|jt jd{Vddtj d|i d}| |d{V}|S#t$rtdicYSwxYw) z'Check if email is registered in Zendeskr&Napplication/jsonr(z Content-Typecustomer_emailPOSTr+datar,zGot IAIDTokenError)r0r1r2IS_REGISTERED_URL_TEMPLATEr4r5rr6rdumpsencode_register_statusrrr?)r8emailr1r#s rcheck_registeredz"AnalystCleanupAPI.check_registereds n,,.553=5II$9$C$E$EEEEEEE$6Z!15 9::AACC-G//88888888FM     LL- . . .III sBB""&C  C c ||}|S#t$r'}td|icYd}~Sd}~wwxYw)r<z&Failed to check email registration: %sN)r1r>rr?r@s rrez"AnalystCleanupAPI._register_statuss` [[))FM    LLA1 E E EIIIIII s A AA A c K tjd{V}n#t$rddtifcYSwxYwtj|j|j |ddtj |||trdndd d }||d{VS) zAsk the backend to open a support ticket. Return an (HTTP status, response body) pair; the status is None when the backend could not be reached at all. Nmessager&r\r] pr_imunify_avpr_im360)rfsubject descriptionproductr_r`)rr6rNO_AGENT_TOKENr0r1r2CREATE_TICKET_URL_TEMPLATEr4r5rrcrrd_send_create_ticket)r8rfrmrntokenr1s r create_ticketzAnalystCleanupAPI.create_tickets  5/9;;;;;;;;EE 5 5 5)^44 4 4 4 5.((  * 1 1s} 1 E E 2"&#.+9Iz   fhh!)  $,,W555555555s 66c tj||j5}|jt |fcdddS#1swxYwYdS#tjj$r)}|j|j t |nifcYd}~Sd}~wt$r)}t d|difcYd}~Sd}~wwxYw)r<)timeoutNz*Failed to reach create-ticket endpoint: %s) r0r1urlopen_SOCKET_TIMEOUTrVr r? HTTPErrorcodefpr>rr)r8r1rrs rrrz%AnalystCleanupAPI._send_create_tickets4 ''!4( ? X(>(>> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|% G G G6ad.><???BF F F F F F F    NNG K K K8OOOOOO sQ&AA  A AAAAC,B C CC;CC)r __module__ __qualname__r3rMrbrqrminr. classmethodr:r r7rFrrRrNrgrertrrrJrr"r"'si8!K; < \F [.  [ 7SE7tf777[7@&&[&,[&[66[6>  [   rJr") http.clientrr urllib.errorr0urllib.requestloggingrrdefence360agent.api.serverr defence360agent.contracts.configrdefence360agent.internals.iaidrrdefence360agent.rpc_tools.utilsr defence360agent.utils.supportr getLoggerrrr/rpr r"rrJrrs> ((((((((******;;;;;;FEEEEE666666  8 $ $ Ib ! ! ! "   ~~~~~~~~~~rJdefence360agent/api/server/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000002624600000000000022733 0ustar r_jddlZddlZddlZddlZddlZddlmZmZddl m Z ddl m Z ddl mZmZddlmZddlmZejeZed Zd Zd ZGd d e ZdS)N)datetime timedelta)API)ANTIVIRUS_MODE)IndependentAgentIDAPIIAIDTokenError)run_in_executor_decorator) parse_params )minutesno_agent_tokencP tj|}nK#tttjjf$r'}td|icYd}~Sd}~wwxYwt|tr|Stdt|j iS)Nz#Cannot decode API response body: %sz&API response body is %s, not an object) jsonload ValueErrorOSErrorhttpclient HTTPExceptionloggerwarning isinstancedicttype__name__)responsebodyes _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/analyst_cleanup.py _json_objectr sy"" !: ;)H)J)J#J#J#J#J#J#JK-G--g66666666F#)CJx &.lnnCJ{ #M    55 s AC%% C32C3c ||}|ddS#t$r&}td|Yd}~dSd}~wwxYw)&Execute the actual request in executorr#Fz&Failed to check cleanup permission: %sNT)r1get Exceptionrerrorr8r1r#rs rr7z AnalystCleanupAPI._check_allowedQso [[))F::h.. .    LLA1 E E E 44444  s*- AAAidsreturncKt|tr dd|D}nt|}|j|j}d|i} tj t||dtj d{Vid}n0#t$r#}td |d}~wwxYw||d{VS) a Retrieve tickets from Zendesk API using the show_many endpoint Args: ids (list or str): List of ticket IDs or comma-separated string of IDs Returns: list: List of dictionaries with 'id', 'status', and 'updated_at' fields ,c34K|]}t|VdS)N)str).0_ids r z0AnalystCleanupAPI.get_tickets..ms(77Cs3xx777777r&rAr(Nr)r*z&Failed to get IAID token for tickets: )rlistjoinrFSHOW_MANY_URL_TEMPLATEr4r5r0r1r2r rr6rrr?_execute_get_tickets)r8rAids_strurlparamsr1rs r get_ticketszAnalystCleanupAPI.get_tickets`s, c4  hh77377777GG#hhG(//S]/CC! n,,VS))!)>)H)J)J#J#J#J#J#J#JK-GG     LLE!EE F F F  --g666666666s,A B66 C#CC#cg} ||}|dgD]T}||d|d|ddU||cS#t$r'}td|Yd}~nd}~wwxYw |S#|ccYSxYw)z2Execute the actual get_tickets request in executorticketsidstatus updated_at)rUrVrWzFailed to get tickets: N)r1r=appendr>rr?)r8r1simplified_ticketsr#ticketrs rrNz&AnalystCleanupAPI._execute_get_ticketss  &[[))F!**Y33  "))$jj.."(**X"6"6&,jj&>&>&& % % % 8 8 8 LL6166 7 7 7 7 7 7 7 7 8 7% %% % % % % % % % %s*BB B:B50C5B::CCcK tj|j|jt jd{Vddtj d|i d}| |d{V}|S#t$rtdicYSwxYw) z'Check if email is registered in Zendeskr&Napplication/jsonr(z Content-Typecustomer_emailPOSTr+datar,zGot IAIDTokenError)r0r1r2IS_REGISTERED_URL_TEMPLATEr4r5rr6rdumpsencode_register_statusrrr?)r8emailr1r#s rcheck_registeredz"AnalystCleanupAPI.check_registereds n,,.553=5II$9$C$E$EEEEEEE$6Z!15 9::AACC-G//88888888FM     LL- . . .III sBB""&C  C c ||}|S#t$r'}td|icYd}~Sd}~wwxYw)r<z&Failed to check email registration: %sN)r1r>rr?r@s rrez"AnalystCleanupAPI._register_statuss` [[))FM    LLA1 E E EIIIIII s A AA A c K tjd{V}n#t$rddtifcYSwxYwtj|j|j |ddtj |||trdndd d }||d{VS) zAsk the backend to open a support ticket. Return an (HTTP status, response body) pair; the status is None when the backend could not be reached at all. Nmessager&r\r] pr_imunify_avpr_im360)rfsubject descriptionproductr_r`)rr6rNO_AGENT_TOKENr0r1r2CREATE_TICKET_URL_TEMPLATEr4r5rrcrrd_send_create_ticket)r8rfrmrntokenr1s r create_ticketzAnalystCleanupAPI.create_tickets  5/9;;;;;;;;EE 5 5 5)^44 4 4 4 5.((  * 1 1s} 1 E E 2"&#.+9Iz   fhh!)  $,,W555555555s 66c tj||j5}|jt |fcdddS#1swxYwYdS#tjj$r)}|j|j t |nifcYd}~Sd}~wt$r)}t d|difcYd}~Sd}~wwxYw)r<)timeoutNz*Failed to reach create-ticket endpoint: %s) r0r1urlopen_SOCKET_TIMEOUTrVr r? HTTPErrorcodefpr>rr)r8r1rrs rrrz%AnalystCleanupAPI._send_create_tickets4 ''!4( ? X(>(>> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|% G G G6ad.><???BF F F F F F F    NNG K K K8OOOOOO sQ&AA  A AAAAC,B C CC;CC)r __module__ __qualname__r3rMrbrqrminr. classmethodr:r r7rFrrRrNrgrertrrrJrr"r"'si8!K; < \F [.  [ 7SE7tf777[7@&&[&,[&[66[6>  [   rJr") http.clientrr urllib.errorr0urllib.requestloggingrrdefence360agent.api.serverr defence360agent.contracts.configrdefence360agent.internals.iaidrrdefence360agent.rpc_tools.utilsr defence360agent.utils.supportr getLoggerrrr/rpr r"rrJrrs> ((((((((******;;;;;;FEEEEE666666  8 $ $ Ib ! ! ! "   ~~~~~~~~~~rJdefence360agent/api/server/__pycache__/cleanup_revert.cpython-311.opt-1.pyc0000644000000000000000000000357700000000000023530 0ustar r_j1ddlZddlmZddlmZddlmZmZddlm Z m Z ej e Z GddeZdS)N)urljoin)Request)APIAPIError)IndependentAgentIDAPIIAIDTokenErrorcFeZdZeejdZedZdS)CleanupRevertAPIz/api/cleanup/revertcBK tjd{V}n#t$rgcYSwxYwt|jd|i} ||d{V}n4#t $r'}td|gcYd}~Sd}~wwxYw|dS)NzX-Auth)headersz'Failed to fetch cleanup revert data: %spaths) r get_tokenrrURL async_requestrloggerwarning)clstokenrequestresultexcs ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/cleanup_revert.pyr zCleanupRevertAPI.pathss /9;;;;;;;;EE   III #'He+<=== ,,W55555555FF    NNDc J J JIIIIII gs* -- A%% B/B BBN) __name__ __module__ __qualname__rr _BASE_URLr classmethodr rr r sA '#-!6 7 7C  [   rr )logging urllib.parserurllib.requestrdefence360agent.api.serverrrdefence360agent.internals.iaidrr getLoggerrrr rrrr&s """"""44444444  8 $ $srdefence360agent/api/server/__pycache__/cleanup_revert.cpython-311.pyc0000644000000000000000000000357700000000000022571 0ustar r_j1ddlZddlmZddlmZddlmZmZddlm Z m Z ej e Z GddeZdS)N)urljoin)Request)APIAPIError)IndependentAgentIDAPIIAIDTokenErrorcFeZdZeejdZedZdS)CleanupRevertAPIz/api/cleanup/revertcBK tjd{V}n#t$rgcYSwxYwt|jd|i} ||d{V}n4#t $r'}td|gcYd}~Sd}~wwxYw|dS)NzX-Auth)headersz'Failed to fetch cleanup revert data: %spaths) r get_tokenrrURL async_requestrloggerwarning)clstokenrequestresultexcs ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/cleanup_revert.pyr zCleanupRevertAPI.pathss /9;;;;;;;;EE   III #'He+<=== ,,W55555555FF    NNDc J J JIIIIII gs* -- A%% B/B BBN) __name__ __module__ __qualname__rr _BASE_URLr classmethodr rr r sA '#-!6 7 7C  [   rr )logging urllib.parserurllib.requestrdefence360agent.api.serverrrdefence360agent.internals.iaidrr getLoggerrrr rrrr&s """"""44444444  8 $ $srdefence360agent/api/server/__pycache__/events.cpython-311.opt-1.pyc0000644000000000000000000000660000000000000022004 0ustar r_jI~ddlZddlZddlZddlmZddlmZddlm Z ej e Z GddeZ dS)N)API)IndependentAgentIDAPI)run_in_executor_decoratorceZdZdZdZdZeedZedZ edZ eedZ dS) EventsAPIzV{base}/api/dashboard/events?dashboard=false&popup=true¬_snoozed_at={not_snoozed_at}z:{base}/api/dashboard/v2/events?notification=1&enduser=truez/{base}/api/dashboard/v2/events?smartadvice=truec 2tj|j|jt tj d}||}|dS)N)basenot_snoozed_atGET)methodresult) urllibrequestRequestADVICES_API_URL_TEMPLATEformat _BASE_URLintdatetimenow timestampclsrr s V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/events.pyadviceszEventsAPI.advicess.((  ( / /]"8#4#8#8#:#:#D#D#F#FGG 0   )  W%%hcKtj|j|jddt jd{Vi}||d{VS)Nr r X-Authr headers) rrrNOTIFICATIONS_API_URL_TEMPLATErrr get_token_send_notificationsrrs r notificationzEventsAPI.notification%s.((  . 5 53= 5 I I%:%D%F%FFFFFFFG)   ,,W555555555rcKtj|j|jddt jd{Vi}||dS)Nrr rr r )rrrSMART_ADVICE_API_URL_TEMPLATErrrr#r%s r smart_adviceszEventsAPI.smart_advices.s{.((  - 4 4#- 4 H H%:%D%F%FFFFFFFG)   {{7##H--rc<||}|dS)Nr )rrs rr$zEventsAPI._send_notifications7s W%%hrN) __name__ __module__ __qualname__rr"r( classmethodrrr&r)r$rrrr s 5 E# :"   [  66[6..[.  [   rr)urllib.requestrloggingrdefence360agent.api.serverrdefence360agent.internals.iaidrdefence360agent.rpc_tools.utilsr getLoggerr+loggerrr/rrr7s******@@@@@@EEEEEE  8 $ $/ / / / / / / / / / rdefence360agent/api/server/__pycache__/events.cpython-311.pyc0000644000000000000000000000660000000000000021045 0ustar r_jI~ddlZddlZddlZddlmZddlmZddlm Z ej e Z GddeZ dS)N)API)IndependentAgentIDAPI)run_in_executor_decoratorceZdZdZdZdZeedZedZ edZ eedZ dS) EventsAPIzV{base}/api/dashboard/events?dashboard=false&popup=true¬_snoozed_at={not_snoozed_at}z:{base}/api/dashboard/v2/events?notification=1&enduser=truez/{base}/api/dashboard/v2/events?smartadvice=truec 2tj|j|jt tj d}||}|dS)N)basenot_snoozed_atGET)methodresult) urllibrequestRequestADVICES_API_URL_TEMPLATEformat _BASE_URLintdatetimenow timestampclsrr s V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/events.pyadviceszEventsAPI.advicess.((  ( / /]"8#4#8#8#:#:#D#D#F#FGG 0   )  W%%hcKtj|j|jddt jd{Vi}||d{VS)Nr r X-Authr headers) rrrNOTIFICATIONS_API_URL_TEMPLATErrr get_token_send_notificationsrrs r notificationzEventsAPI.notification%s.((  . 5 53= 5 I I%:%D%F%FFFFFFFG)   ,,W555555555rcKtj|j|jddt jd{Vi}||dS)Nrr rr r )rrrSMART_ADVICE_API_URL_TEMPLATErrrr#r%s r smart_adviceszEventsAPI.smart_advices.s{.((  - 4 4#- 4 H H%:%D%F%FFFFFFFG)   {{7##H--rc<||}|dS)Nr )rrs rr$zEventsAPI._send_notifications7s W%%hrN) __name__ __module__ __qualname__rr"r( classmethodrrr&r)r$rrrr s 5 E# :"   [  66[6..[.  [   rr)urllib.requestrloggingrdefence360agent.api.serverrdefence360agent.internals.iaidrdefence360agent.rpc_tools.utilsr getLoggerr+loggerrr/rrr7s******@@@@@@EEEEEE  8 $ $/ / / / / / / / / / rdefence360agent/api/server/__pycache__/reputation.cpython-311.opt-1.pyc0000644000000000000000000000771600000000000022703 0ustar r_jddlZddlZddlZddlZddlZddlmZddlZddl Z ddl m Z m Z ddl mZmZe jeZGddeZdS)N)List)retry_onsplit_for_chunk)APIAPIErrorceZdZdZdZdZdZdZdZe de e de e fd Z e de e de e fd Ze eee de fd Ze eee d e fdZdS) ReputationAPIz/api/reputation/checkz/api/reputation/resulti i<domainsreturncKtd|tj}|d|j|d{VS)NzDomainListRequest domains: %s)loggerinfoasyncioget_event_looprun_in_executor_check)clsr loops Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/reputation.pycheckzReputationAPI.checksU 3W===%''))$ GDDDDDDDDDcg}t||jD]7}||}||d}||z }8|S)N result_id)r CHUNK_SIZE _check_chunk _get_result)rr result_listchunkresult next_chunks rrzReputationAPI._check"s] $Wcn== & &E%%e,,F )<==J : %KKr)timeoutc tj|j|jzdddit jt|}||S)NPOSTz Content-Typezapplication/json)r )methodheadersdata) urllibrequestRequest _BASE_URL REQUEST_URLjsondumpsdictencode)rr check_requests rrzReputationAPI._check_chunk+sp.. MCO +#%78D///007799 /  {{=)))rrcrt|}d|j|jztj|}tj|}||}|d}|(tj |j td|S)N)rz{}?{}r!zResponse not ready yet) r0formatr, RESULT_URLr)parse urlencoder*r+timesleepWAIT_BEFORE_RETRYr)rrr(urlr*responser!s rrzReputationAPI._get_result6si(((nn MCN *FL,B,B4,H,H  .((--;;w''(# > Js, - - -344 4 rN)__name__ __module__ __qualname__r-r5rr:WAIT_FOR_RESULT_SOCKET_TIMEOUT classmethodrstrr0rrrrrrrrr r s')K)J JOOE$s)ET EEE[E T#Y4:[ Xh000*D***10[* Xh000 C   10[   rr )r. urllib.errorr)urllib.request urllib.parsertypingrr8loggingdefence360agent.utilsrrdefence360agent.api.serverrr getLoggerr=rr rDrrrMs  ;;;;;;;;44444444  8 $ $44444C44444rdefence360agent/api/server/__pycache__/reputation.cpython-311.pyc0000644000000000000000000000771600000000000021744 0ustar r_jddlZddlZddlZddlZddlZddlmZddlZddl Z ddl m Z m Z ddl mZmZe jeZGddeZdS)N)List)retry_onsplit_for_chunk)APIAPIErrorceZdZdZdZdZdZdZdZe de e de e fd Z e de e de e fd Ze eee de fd Ze eee d e fdZdS) ReputationAPIz/api/reputation/checkz/api/reputation/resulti i<domainsreturncKtd|tj}|d|j|d{VS)NzDomainListRequest domains: %s)loggerinfoasyncioget_event_looprun_in_executor_check)clsr loops Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/reputation.pycheckzReputationAPI.checksU 3W===%''))$ GDDDDDDDDDcg}t||jD]7}||}||d}||z }8|S)N result_id)r CHUNK_SIZE _check_chunk _get_result)rr result_listchunkresult next_chunks rrzReputationAPI._check"s] $Wcn== & &E%%e,,F )<==J : %KKr)timeoutc tj|j|jzdddit jt|}||S)NPOSTz Content-Typezapplication/json)r )methodheadersdata) urllibrequestRequest _BASE_URL REQUEST_URLjsondumpsdictencode)rr check_requests rrzReputationAPI._check_chunk+sp.. MCO +#%78D///007799 /  {{=)))rrcrt|}d|j|jztj|}tj|}||}|d}|(tj |j td|S)N)rz{}?{}r!zResponse not ready yet) r0formatr, RESULT_URLr)parse urlencoder*r+timesleepWAIT_BEFORE_RETRYr)rrr(urlr*responser!s rrzReputationAPI._get_result6si(((nn MCN *FL,B,B4,H,H  .((--;;w''(# > Js, - - -344 4 rN)__name__ __module__ __qualname__r-r5rr:WAIT_FOR_RESULT_SOCKET_TIMEOUT classmethodrstrr0rrrrrrrrr r s')K)J JOOE$s)ET EEE[E T#Y4:[ Xh000*D***10[* Xh000 C   10[   rr )r. urllib.errorr)urllib.request urllib.parsertypingrr8loggingdefence360agent.utilsrrdefence360agent.api.serverrr getLoggerr=rr rDrrrMs  ;;;;;;;;44444444  8 $ $44444C44444rdefence360agent/api/server/__pycache__/send_message.cpython-311.opt-1.pyc0000644000000000000000000010413700000000000023141 0ustar r_jb:ddlZddlZddlZddlZddlZddlZddlZ ddlZddl Zddl ZdZ ej j Zejj jZn)#e$r!dZ GddeZGddeZYnwxYwddlZddlmZmZdd lmZdd lmZddlZddlZdd lmZmZm Z m!Z!m"Z"dd l#m$Z$dd l%m&Z&m'Z'ddl(m)Z)ddl*m+Z+m,Z,ddl-m.Z.ddl/m0Z0m1Z1ddl2m3Z3m4Z4ddl5m6Z6ddl7m8Z8ee9Z:e3Z;e3Zde?fdZ@deAdeBddfdZCdZDdZEGddeZFeGddhZHd ZIdeJfd!ZKd"eAdeAfd#ZLdeMfd$ZNd%ZOd&ZPeIfd'eAfd(ZQd)eAfd*ZRd+eddfd,ZSGd-d.eeZTGd/d0eTZUGd1d2eUZVGd3d4ZWdS)5NTFceZdZdS)_NATSMaxPayloadErrorN)__name__ __module__ __qualname__\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/send_message.pyrrs r rceZdZdZdS) _NATSAPIErrorN)rrrerr_coderr r r r sr r )ABCabstractmethod) getLogger)Optional)APIAPIError APITokenErrorFGWSendMessgeExceptionNATSSendMessageException)Core) estimate_sizeMessage) delivery_ack)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)g)IndependentAgentIDAPIIAIDTokenError)Gen publisher) AsyncIterate)ServerJSONEncoderreturnctdc}a|S)z:Method-less drops since the last call, then reset (delta).r)_method_missing_dropped_deltavalues r pop_method_missing_droppedr)As,I!(E ( Lr messagesinkc tdz atdz atd||d|d|dt |tdS)z*Count and log a message no sink can route.zkDropping message without a method: sink=%s message_id=%s plugin_id=%s timestamp=%s keys=%s dropped_total=%d message_id plugin_id timestampN)_method_missing_dropped_totalr&loggererrorgetsorted)r*r+s r _drop_method_lessr6Hs{"Q&!!Q&! LL >  L!! K   K  w%     r i]'iF'ceZdZdZdS) _StreamFullzDStream is at capacity: re-queue the rest, the connection is healthy.N)rrr__doc__rr r r8r8dsNNNNr r8scanidscan_id cPt|tr d|DSt}|D]s}t|tr||-t|tr1|d|Dt|S)zrStrings a sibling map could be keyed on: a dict's own keys, or the scalar values carried by a list's elements.c<h|]}t|t|Sr isinstancestr).0keys r z_element_ids..vs'AAAJsC,@,@AAAAr c3DK|]}t|t|VdSNr?)rBr(s r z_element_ids..|sEz%7M7Mr )r@dictsetrAaddupdatevalues) containeridselements r _element_idsrPrs)T""BAAyAAAA %%C gs # #  GGG      & &  JJ#*>>#3#3    Jr fieldsc`dDfdDS)zSibling dicts keyed entirely by a field's elements, which have to follow those records rather than be bisected away from them.c4i|]\}}|t|Sr)rPrBnamer(s r z _paired_maps..s& G G Gu4e$$ G G Gr cVi|]$fdD%S)ch|]=\}}|kr2t|tr|rt|k;|>Sr)r@rHrI)rBotherr(rNrUs r rDz*_paired_maps...sc   u}}5$''E c$i''  (''r items)rBrUrQrNs @r rVz _paired_maps..s_          &       r rZ)rQrNs`@r _paired_mapsr\sT H G  G G GC         r cbt|tot| SrF)r@rH _INDEX_KEYS isdisjointr's r _carries_index_keyr`s( eT " " H;+A+A%+H+H'HHr c|dkrdSt|trt||dz St|trt ||dz SdS)Nrr-)r@rH_split_largest_fieldlist_split_container)rOdepths r _split_elementrfsa zzt'4  8#GUQY777'4  4333 4r cttrttdkr>tdz}fdd|Dfd|dDgSsdSt d|}|dSfd|DStdkr&tdz}d||dgSsdSt d|}|dSd|DS) zxBisect a list or dict, descending into a lone element so a single oversized record can still be split within itself.r-c"i|] }|| SrrrBrCr(s r rVz$_split_container..777SeCj777r Nc"i|] }|| Srrrjs r rVz$_split_container..rkr rc$g|] }d|i S)rr)rBhalfkeyss r z$_split_container..s!222Da$222r cg|]}|gSrr)rBrns r rpz$_split_container..s % % %tTF % % %r )r@rHrclenrf)r(remidinnerros` @r rdrdsJ% 3E{{ t99q==d))q.C7777D#J7777777DJ777  4uT!W~u55 =42222E2222 5zzA~~%jjAodsd U344[)) t 58U + +E }t % %u % % %%r itemc  d|D t  d D fd D} fd}t||dD]}t ||}|ndSg}|D]b}t | i|||i} |D]+} fd | D|| <,||c|S) zBisect the heaviest payload field, while index-carrying fields and maps paired with another field's records ride along instead of being split.cRi|]$\}}t|ttf!||%Sr)r@rcrHrTs r rVz(_split_largest_field..sB D% edD\ * * er ch|] }|D]}| Srr)rBmapsrUs r rDz'_split_largest_field..s%CCC$dCCdCCCCr c@g|]\}}|v t||Sr)r`)rBrUr( followerss r rpz(_split_largest_field..s@ D% y );E)B)B  r crt|tfd|DzS)Nc3BK|]}t|VdSrF)r)rBrYrQs r rGz7_split_largest_field..weight..s@1 1 -2M&- ( (1 1 1 1 1 1 r )rsum)rUrQpaireds r weightz$_split_largest_field..weightsRVD\**S1 1 1 1 6.s0C$;;U;;r )r[r\rLr5rdrPappend)rure candidatesrfieldhalvespartsrnpartrUrQr{rrs @@@@r rbrbs ::<<F & ! !FCC&--//CCCI!<<>>J        ===!&-77   E t E  D!!$$$t$$5M  D"(,"4"4"6"6DJJ  T Lr loadedcd}t|trEt|dkr2t|dz}id|d|iid||digSt|trt|dkr|dnd}t|tr2 t |}n#t $rYdSwxYw|fd|DSdS)zSplit an oversized message into smaller parts. Returns a list of parts, or None when the message carries a single irreducible record.r[r-rhNrc"g|] }id|gi SrZr)rBrnrs r rpz$_split_oversized..s+CCCD/v/w//CCCr )r4r@rcrrrHrbRecursionError)rr[rssinglers` r _split_oversizedrs! JJw  E% 3u::>>%jjAo ,v ,wdsd , , ,v ,wcdd , ,  $E400 NSZZ1__U1XX$F&$ D )&11FF   44    CCCCFCCC C 4s9C CCexc@Ktd|dS)aDowngrade nats-py internal errors to DEBUG. Transient errors (ConnectionRefused, AuthorizationViolation) are expected during agent restarts. Our code already logs a WARNING with context, so the nats-py default ERROR + traceback is noise. znats: %sN)r2debug)rs r _nats_error_cbrs" LLR     r cReZdZdZedefdZdeddfdZdede ddfd Z dS) BaseSendMessageAPIz/api/v2/send-message/{method}r$c KdSrFr)selfmessage_methodheaders post_datas r _send_requestz BaseSendMessageAPI._send_requests  r resultNcd|vr"td||ddkr5td|ddS)Nstatusz unexpected server response: {!r}okzserver error: {}msg)rformatr4)rrs r check_responsez!BaseSendMessageAPI.check_responsesk 6 ! !=DDVLLMM M ( t # #-44VZZ5F5FGGHH H $ #r methodrcK tjd{V}n$#t$r}td|d}~wwxYwd|d}||||d{V}||dS)NzIAID token error occurred zapplication/json)z Content-TypezX-Auth)r get_tokenrrrr)rrrtokenerrs r send_datazBaseSendMessageAPI.send_datas B/9;;;;;;;;EE B B B @Q @ @AA A B/  ))&'9EEEEEEEE F#####s ?:?) rrrURLrrHrrrAbytesrrr r rrs )C    ^ ITIdIIII $c $e $ $ $ $ $ $ $r rceZdZejZddedefdZdeddfdZde eddfd Z d e ddfd Z d Z d eddfdZdS)SendMessageAPINrpm_verbase_urlcz||_||_d|_d|_i|_|r ||_dS|j|_dS)N) _executorr product_name server_idlicenser _BASE_URL)rrrexecutors r __init__zSendMessageAPI.__init__+sF!   +$DMMM NDMMMr rr$c||_dSrF)r)rrs r set_product_namezSendMessageAPI.set_product_name6s(r rc||_dSrF)r)rrs r set_server_idzSendMessageAPI.set_server_id9s "r rc||_dSrF)r)rrs r set_licensezSendMessageAPI.set_license<s  r cKtj|j|j|z||d}|||jd{VS)NrPOST)datarr)r)urllibrequestRequestrrr async_requestr)rrrrrs r rzSendMessageAPI._send_request?st.(( MDHOO>OBB B )   ''$.'IIIIIIIIIr r*cK|dst|ddSd|vrtj|d<d|vrtjj|d<|j|j|j|j |j d}tj |t}||j|d{VdS)Nrhttpr0r.)payloadrr.rrU)cls)r4r6timeuuiduuid4hexrrr.rrjsondumpsr#encoderr)rr* data2sendrs r send_messagezSendMessageAPI.send_messageHs{{8$$  gv . . . F g % %#'9;;GK w & &$(JLL$4GL !|!,%   Jy.?@@@GGII nnW^Y77777777777r )NN)rrrrDEFAULT_SOCKET_TIMEOUT_SOCKET_TIMEOUTrArrrrrHrrrrrr r rr(s1O + + +s + + + +)S)T))))#x}#####4DJJJ8'8d888888r rcFeZdZdefdZdeeeefddfdZ dS)FileBasedGatewayAPIr$cK|4d{Vtjtj|d{V}|dd|Ddcdddd{VS#1d{VswxYwYdS)Nrc&i|]\}}|dk ||Srr)rBkvs r rVz8FileBasedGatewayAPI._prepare_message..es#JJJ$!QAMMAMMMr )rr)asyncio to_threadrloadsr[)rr* semaphorers r _prepare_messagez$FileBasedGatewayAPI._prepare_message`s        ",TZAAAAAAAAF *JJ&,,..JJJ                              sA A** A47A4messagesNcKd}tj|fdt|2d{V}tj|d{V}|D]N}i|did|ddi}t j|tdOtjtj |d{V}tj dd }tj |d } | d d g} tj| tjjtjjtjjd d{V} t%j|} | | d{V\} }t-jdr*t.dt3|| || jdkr`t.d|t;t=d||D]:}t>j !|dd;dS)NcTKg|3d{V \}}|"6SrF)r)rB_rrrs r rpz5FileBasedGatewayAPI.send_messages..ksd         a  ! !#y 1 1    s(rrrzagent-fgw-sendingstageI360_MESSAGE_GATEWAY_BIN_PATHz /usr/libexec/zimunify-message-gatewayz send-manyz"--producer=i360-agent-non-resident)stdinstdoutstderr)inputDEBUGzMessage sent to fgw: %s %s %srzError sending message: r.)"r Semaphorer"gatherr4r!report_reporter_gen_fgwrrrosgetenvpathjoincreate_subprocess_exec subprocessPIPEbase64 b64encoder communicaterr2inforr returncoder3decoderrArregistryconfirm)rr max_threadstasksprepared_messagesrflatdumped_messages bin_file_pathbin_filecommandprocessb64datarrrs` @r send_messagesz!FileBasedGatewayAPI.send_messageshs %k22      ,X 6 6         #*.%"8888888$  CKcggfb))K8SWWXr5J5JKKD  '/B     !( 1 J)! !        +_  7<< /HII   0   6 $)%*%*           "?#9#9#;#;<<&222AAAAAAAA 5>>  KK/X      " " LLD6==??DD E E E(?fmmoo??@@ % I IC  ! ) )#f+//,*G*G H H H H I Ir ) rrrrHrrctuplefloatrr rr r rr_sgD2IDue|1D,E2I$2I2I2I2I2I2Ir rceZdZdZdZdZdZdZdZdZ dZ de fd Z e d Zd Zd eeeefdd fdZdZdZd S)NATSGatewayAPIzPublishes messages to the embedded NATS server via localhost TCP. Connects to nats://127.0.0.1: with an auth token read from a file written by the resident-agent on startup. z imunify.api.iz/var/run/imunify360/nats.tokenz/var/run/imunify360/nats.addrrc>d|_d|_d|_d|_dS)Nr)_nc_last_connect_attempt_oversized_dropped_oversized_rejectedrs r rzNATSGatewayAPI.__init__s'%&""##$   r r$c$|jdc}|_|S)z=Oversized rejections since the last call, then reset (delta).r)r)rr(s r pop_oversized_rejectedz%NATSGatewayAPI.pop_oversized_rejecteds*.*BA't' r ctjdtj} t |5}|}dddn #1swxYwY|r|Sn#t$rYnwxYwttjdttj }d|S)zBRead NATS listen address from addr file, fall back to env/default.I360_NATS_ADDR_PATHNI360_NATS_PORTz 127.0.0.1:) rrrDEFAULT_ADDR_PATHopenreadstripOSErrorintrA DEFAULT_PORT) addr_pathfaddrports r _read_addrzNATSGatewayAPI._read_addrsI !>#C   i (Avvxx~~'' ( ( ( ( ( ( ( ( ( ( ( ( ( ( (       D  I&N,G(H(H I I  #D"""s4A3'A# A3#A''A3*A'+A33 B?BcK|j|jjrdStstdt j}||jz }||jkrtd|j|z dd||_|d{V| }tj d|j } t|5}|}dddn #1swxYwYt!jd|||jdt&d{V|_t(d |dS#t,$r}td ||d}~wwxYw) Nznats-py is not installedzNATS reconnect backoff (z.1fz s remaining)I360_NATS_TOKEN_PATHznats://r)rconnect_timeoutmax_reconnect_attemptserror_cbzConnected to NATS at %szFailed to connect to NATS: )r is_connected _has_natsrr monotonicrMIN_RECONNECT_INTERVAL_closer%rrDEFAULT_TOKEN_PATHrrrnatsconnectCONNECT_TIMEOUTrr2r Exception)rnow since_lastr# token_pathr"rrs r _ensure_connectedz NATSGatewayAPI._ensure_connecteds  8 DH$9  F G*+EFF Fn455 3 3 3*P0:=OPPP &)"kkmm  Y5t7NOO  j!! )Q(( ) ) ) ) ) ) ) ) ) ) ) ) ) ) )!\ $   $ 4'(' DH KK14 8 8 8 8 8   *1a11  s=E'D8 EDE D AE E?'E::E?rNc K|d{Vd} |j}|D]\}} tj|}nC#tjt f$r*}td||dz }Yd}~Wd}~wwxYw| dst|d|dz }| d}|j |z} tj|| dfg} g} | r| \} } tj| }| rd| ind} || ||d{V}nY#t&t(f$rD}t+|t(r:|jt.krt1d | d | d |||jt2krt5| }|A| | t9|t;||dd fYd}~| p%t=j| }tC|D](\}}|d |}||d<| ||f)td| | t9|t9|Yd}~d}~wwxYwtEj dr't#d| |j$|j%| | r|xj&dz c_&tOtPr|xj)dz c_)| d\}}}}t*d| | dt9| tWd| Dd| D|j&|| nTtYj-i|d|it\dt^j01| d|dz }dS#t0$rU}td|t9|t9||z |ted|||d}~wtf$r^}|4d{Vtd|t9||ted|||d}~wwxYw)NrzSkipping malformed message: %sr-rr1r.z Nats-Msg-Id)rzsubject=z message_id=z: .zKSplitting oversized NATS message: subject=%s message_id=%s size=%d parts=%drz"Published to %s, stream=%s, seq=%sz~Dropping oversized NATS message: subject=%s message_id=%s parts=%d size=%d fragments=%s oversized_total=%d error=%s preview=%rc3$K|] \}}}}|V dSrFr)rBrsizes r rGz/NATSGatewayAPI.send_messages..`s*>>]QaD>>>>>>r cg|] \}}}}| Srr)rBfragrs r rpz0NATSGatewayAPI.send_messages..as ;;;-$1a;;;r zagent-nats-sendingrz$NN!)3w<<Q# O%HHHH$I W(=(=(G(G(I(I!,5V+<+<>>KE4+3(=(=e(=(=I1:D.#NND)+<====>#$LLKK !S)!TuW~~ @#JG g8@L++q0++!"ABB600A500+21:(Aq%LLB  <00G >>g>>>>>;;7;;;/"$4648V44*2 !)11&**\2J2JKKKQ ow w r    NNNH H )    +*q**#    ++--        NN>H      +2q22#  s!PAPB- B PBCPE97P9K BK &P,BK P KEP S#AQ33 SASScK|jF |jd{Vn#t$rYnwxYwd|_dS#d|_wxYwdSrF)rcloser4rs r r/zNATSGatewayAPI._closes 8  hnn&&&&&&&&&&     4 s!+A 8A8A A c>K|d{VdSrF)r/rs r r`zNATSGatewayAPI.closes,kkmmr )rrrr9rFr r0rr3r.rrr staticmethodr%r8rcr r rr r/r`rr r rrs )L97O%%%  ##\#&###JYDue|1D,EY$YYYYv    r r)XrrGrMrrr urllib.errorrr1 nats.errorsnats.js.errorsr,errorsMaxPayloadErrorrrTrr ImportErrorr4urllib.requestabcrrloggingrtypingrrrdefence360agent.api.serverrrrr defence360agent.contracts.configr"defence360agent.contracts.messagesrrdefence360agent.internalsr'defence360agent.internals.feature_flagsrr&defence360agent.internals.global_scoperdefence360agent.internals.iaidrr2defence360agent.internals.message_status_publisherr r!!defence360agent.utils.async_utilsr"defence360agent.utils.jsonr#rr2rrSr1r&rr)rHrAr6rKrLr8 frozensetr^_MAX_SPLIT_DEPTHrIrPr\boolr`rfrdrbrrrrrrrr r rzs   KKKI;6GN+MMI     y     ######## 211111EEEEEEEE222222544444NMMMMMMM::::::888888 8  CEESUU ! !Ct34*OOOOO)OOOi9-..  s     $"IIIII&&&8,<,,t,,,,^T0!Y!4!!!!$$$$$c$$$44848484848'484848n;I;I;I;I;I.;I;I;I|{{{{{{{{{{s+A #A0/A0defence360agent/api/server/__pycache__/send_message.cpython-311.pyc0000644000000000000000000010413700000000000022202 0ustar r_jb:ddlZddlZddlZddlZddlZddlZddlZ ddlZddl Zddl ZdZ ej j Zejj jZn)#e$r!dZ GddeZGddeZYnwxYwddlZddlmZmZdd lmZdd lmZddlZddlZdd lmZmZm Z m!Z!m"Z"dd l#m$Z$dd l%m&Z&m'Z'ddl(m)Z)ddl*m+Z+m,Z,ddl-m.Z.ddl/m0Z0m1Z1ddl2m3Z3m4Z4ddl5m6Z6ddl7m8Z8ee9Z:e3Z;e3Zde?fdZ@deAdeBddfdZCdZDdZEGddeZFeGddhZHd ZIdeJfd!ZKd"eAdeAfd#ZLdeMfd$ZNd%ZOd&ZPeIfd'eAfd(ZQd)eAfd*ZRd+eddfd,ZSGd-d.eeZTGd/d0eTZUGd1d2eUZVGd3d4ZWdS)5NTFceZdZdS)_NATSMaxPayloadErrorN)__name__ __module__ __qualname__\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/api/server/send_message.pyrrs r rceZdZdZdS) _NATSAPIErrorN)rrrerr_coderr r r r sr r )ABCabstractmethod) getLogger)Optional)APIAPIError APITokenErrorFGWSendMessgeExceptionNATSSendMessageException)Core) estimate_sizeMessage) delivery_ack)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)g)IndependentAgentIDAPIIAIDTokenError)Gen publisher) AsyncIterate)ServerJSONEncoderreturnctdc}a|S)z:Method-less drops since the last call, then reset (delta).r)_method_missing_dropped_deltavalues r pop_method_missing_droppedr)As,I!(E ( Lr messagesinkc tdz atdz atd||d|d|dt |tdS)z*Count and log a message no sink can route.zkDropping message without a method: sink=%s message_id=%s plugin_id=%s timestamp=%s keys=%s dropped_total=%d message_id plugin_id timestampN)_method_missing_dropped_totalr&loggererrorgetsorted)r*r+s r _drop_method_lessr6Hs{"Q&!!Q&! LL >  L!! K   K  w%     r i]'iF'ceZdZdZdS) _StreamFullzDStream is at capacity: re-queue the rest, the connection is healthy.N)rrr__doc__rr r r8r8dsNNNNr r8scanidscan_id cPt|tr d|DSt}|D]s}t|tr||-t|tr1|d|Dt|S)zrStrings a sibling map could be keyed on: a dict's own keys, or the scalar values carried by a list's elements.c<h|]}t|t|Sr isinstancestr).0keys r z_element_ids..vs'AAAJsC,@,@AAAAr c3DK|]}t|t|VdSNr?)rBr(s r z_element_ids..|sEz%7M7Mr )r@dictsetrAaddupdatevalues) containeridselements r _element_idsrPrs)T""BAAyAAAA %%C gs # #  GGG      & &  JJ#*>>#3#3    Jr fieldsc`dDfdDS)zSibling dicts keyed entirely by a field's elements, which have to follow those records rather than be bisected away from them.c4i|]\}}|t|Sr)rPrBnamer(s r z _paired_maps..s& G G Gu4e$$ G G Gr cVi|]$fdD%S)ch|]=\}}|kr2t|tr|rt|k;|>Sr)r@rHrI)rBotherr(rNrUs r rDz*_paired_maps...sc   u}}5$''E c$i''  (''r items)rBrUrQrNs @r rVz _paired_maps..s_          &       r rZ)rQrNs`@r _paired_mapsr\sT H G  G G GC         r cbt|tot| SrF)r@rH _INDEX_KEYS isdisjointr's r _carries_index_keyr`s( eT " " H;+A+A%+H+H'HHr c|dkrdSt|trt||dz St|trt ||dz SdS)Nrr-)r@rH_split_largest_fieldlist_split_container)rOdepths r _split_elementrfsa zzt'4  8#GUQY777'4  4333 4r cttrttdkr>tdz}fdd|Dfd|dDgSsdSt d|}|dSfd|DStdkr&tdz}d||dgSsdSt d|}|dSd|DS) zxBisect a list or dict, descending into a lone element so a single oversized record can still be split within itself.r-c"i|] }|| SrrrBrCr(s r rVz$_split_container..777SeCj777r Nc"i|] }|| Srrrjs r rVz$_split_container..rkr rc$g|] }d|i S)rr)rBhalfkeyss r z$_split_container..s!222Da$222r cg|]}|gSrr)rBrns r rpz$_split_container..s % % %tTF % % %r )r@rHrclenrf)r(remidinnerros` @r rdrdsJ% 3E{{ t99q==d))q.C7777D#J7777777DJ777  4uT!W~u55 =42222E2222 5zzA~~%jjAodsd U344[)) t 58U + +E }t % %u % % %%r itemc  d|D t  d D fd D} fd}t||dD]}t ||}|ndSg}|D]b}t | i|||i} |D]+} fd | D|| <,||c|S) zBisect the heaviest payload field, while index-carrying fields and maps paired with another field's records ride along instead of being split.cRi|]$\}}t|ttf!||%Sr)r@rcrHrTs r rVz(_split_largest_field..sB D% edD\ * * er ch|] }|D]}| Srr)rBmapsrUs r rDz'_split_largest_field..s%CCC$dCCdCCCCr c@g|]\}}|v t||Sr)r`)rBrUr( followerss r rpz(_split_largest_field..s@ D% y );E)B)B  r crt|tfd|DzS)Nc3BK|]}t|VdSrF)r)rBrYrQs r rGz7_split_largest_field..weight..s@1 1 -2M&- ( (1 1 1 1 1 1 r )rsum)rUrQpaireds r weightz$_split_largest_field..weightsRVD\**S1 1 1 1 6.s0C$;;U;;r )r[r\rLr5rdrPappend)rure candidatesrfieldhalvespartsrnpartrUrQr{rrs @@@@r rbrbs ::<<F & ! !FCC&--//CCCI!<<>>J        ===!&-77   E t E  D!!$$$t$$5M  D"(,"4"4"6"6DJJ  T Lr loadedcd}t|trEt|dkr2t|dz}id|d|iid||digSt|trt|dkr|dnd}t|tr2 t |}n#t $rYdSwxYw|fd|DSdS)zSplit an oversized message into smaller parts. Returns a list of parts, or None when the message carries a single irreducible record.r[r-rhNrc"g|] }id|gi SrZr)rBrnrs r rpz$_split_oversized..s+CCCD/v/w//CCCr )r4r@rcrrrHrbRecursionError)rr[rssinglers` r _split_oversizedrs! JJw  E% 3u::>>%jjAo ,v ,wdsd , , ,v ,wcdd , ,  $E400 NSZZ1__U1XX$F&$ D )&11FF   44    CCCCFCCC C 4s9C CCexc@Ktd|dS)aDowngrade nats-py internal errors to DEBUG. Transient errors (ConnectionRefused, AuthorizationViolation) are expected during agent restarts. Our code already logs a WARNING with context, so the nats-py default ERROR + traceback is noise. znats: %sN)r2debug)rs r _nats_error_cbrs" LLR     r cReZdZdZedefdZdeddfdZdede ddfd Z dS) BaseSendMessageAPIz/api/v2/send-message/{method}r$c KdSrFr)selfmessage_methodheaders post_datas r _send_requestz BaseSendMessageAPI._send_requests  r resultNcd|vr"td||ddkr5td|ddS)Nstatusz unexpected server response: {!r}okzserver error: {}msg)rformatr4)rrs r check_responsez!BaseSendMessageAPI.check_responsesk 6 ! !=DDVLLMM M ( t # #-44VZZ5F5FGGHH H $ #r methodrcK tjd{V}n$#t$r}td|d}~wwxYwd|d}||||d{V}||dS)NzIAID token error occurred zapplication/json)z Content-TypezX-Auth)r get_tokenrrrr)rrrtokenerrs r send_datazBaseSendMessageAPI.send_datas B/9;;;;;;;;EE B B B @Q @ @AA A B/  ))&'9EEEEEEEE F#####s ?:?) rrrURLrrHrrrAbytesrrr r rrs )C    ^ ITIdIIII $c $e $ $ $ $ $ $ $r rceZdZejZddedefdZdeddfdZde eddfd Z d e ddfd Z d Z d eddfdZdS)SendMessageAPINrpm_verbase_urlcz||_||_d|_d|_i|_|r ||_dS|j|_dS)N) _executorr product_name server_idlicenser _BASE_URL)rrrexecutors r __init__zSendMessageAPI.__init__+sF!   +$DMMM NDMMMr rr$c||_dSrF)r)rrs r set_product_namezSendMessageAPI.set_product_name6s(r rc||_dSrF)r)rrs r set_server_idzSendMessageAPI.set_server_id9s "r rc||_dSrF)r)rrs r set_licensezSendMessageAPI.set_license<s  r cKtj|j|j|z||d}|||jd{VS)NrPOST)datarr)r)urllibrequestRequestrrr async_requestr)rrrrrs r rzSendMessageAPI._send_request?st.(( MDHOO>OBB B )   ''$.'IIIIIIIIIr r*cK|dst|ddSd|vrtj|d<d|vrtjj|d<|j|j|j|j |j d}tj |t}||j|d{VdS)Nrhttpr0r.)payloadrr.rrU)cls)r4r6timeuuiduuid4hexrrr.rrjsondumpsr#encoderr)rr* data2sendrs r send_messagezSendMessageAPI.send_messageHs{{8$$  gv . . . F g % %#'9;;GK w & &$(JLL$4GL !|!,%   Jy.?@@@GGII nnW^Y77777777777r )NN)rrrrDEFAULT_SOCKET_TIMEOUT_SOCKET_TIMEOUTrArrrrrHrrrrrr r rr(s1O + + +s + + + +)S)T))))#x}#####4DJJJ8'8d888888r rcFeZdZdefdZdeeeefddfdZ dS)FileBasedGatewayAPIr$cK|4d{Vtjtj|d{V}|dd|Ddcdddd{VS#1d{VswxYwYdS)Nrc&i|]\}}|dk ||Srr)rBkvs r rVz8FileBasedGatewayAPI._prepare_message..es#JJJ$!QAMMAMMMr )rr)asyncio to_threadrloadsr[)rr* semaphorers r _prepare_messagez$FileBasedGatewayAPI._prepare_message`s        ",TZAAAAAAAAF *JJ&,,..JJJ                              sA A** A47A4messagesNcKd}tj|fdt|2d{V}tj|d{V}|D]N}i|did|ddi}t j|tdOtjtj |d{V}tj dd }tj |d } | d d g} tj| tjjtjjtjjd d{V} t%j|} | | d{V\} }t-jdr*t.dt3|| || jdkr`t.d|t;t=d||D]:}t>j !|dd;dS)NcTKg|3d{V \}}|"6SrF)r)rB_rrrs r rpz5FileBasedGatewayAPI.send_messages..ksd         a  ! !#y 1 1    s(rrrzagent-fgw-sendingstageI360_MESSAGE_GATEWAY_BIN_PATHz /usr/libexec/zimunify-message-gatewayz send-manyz"--producer=i360-agent-non-resident)stdinstdoutstderr)inputDEBUGzMessage sent to fgw: %s %s %srzError sending message: r.)"r Semaphorer"gatherr4r!report_reporter_gen_fgwrrrosgetenvpathjoincreate_subprocess_exec subprocessPIPEbase64 b64encoder communicaterr2inforr returncoder3decoderrArregistryconfirm)rr max_threadstasksprepared_messagesrflatdumped_messages bin_file_pathbin_filecommandprocessb64datarrrs` @r send_messagesz!FileBasedGatewayAPI.send_messageshs %k22      ,X 6 6         #*.%"8888888$  CKcggfb))K8SWWXr5J5JKKD  '/B     !( 1 J)! !        +_  7<< /HII   0   6 $)%*%*           "?#9#9#;#;<<&222AAAAAAAA 5>>  KK/X      " " LLD6==??DD E E E(?fmmoo??@@ % I IC  ! ) )#f+//,*G*G H H H H I Ir ) rrrrHrrctuplefloatrr rr r rr_sgD2IDue|1D,E2I$2I2I2I2I2I2Ir rceZdZdZdZdZdZdZdZdZ dZ de fd Z e d Zd Zd eeeefdd fdZdZdZd S)NATSGatewayAPIzPublishes messages to the embedded NATS server via localhost TCP. Connects to nats://127.0.0.1: with an auth token read from a file written by the resident-agent on startup. z imunify.api.iz/var/run/imunify360/nats.tokenz/var/run/imunify360/nats.addrrc>d|_d|_d|_d|_dS)Nr)_nc_last_connect_attempt_oversized_dropped_oversized_rejectedrs r rzNATSGatewayAPI.__init__s'%&""##$   r r$c$|jdc}|_|S)z=Oversized rejections since the last call, then reset (delta).r)r)rr(s r pop_oversized_rejectedz%NATSGatewayAPI.pop_oversized_rejecteds*.*BA't' r ctjdtj} t |5}|}dddn #1swxYwY|r|Sn#t$rYnwxYwttjdttj }d|S)zBRead NATS listen address from addr file, fall back to env/default.I360_NATS_ADDR_PATHNI360_NATS_PORTz 127.0.0.1:) rrrDEFAULT_ADDR_PATHopenreadstripOSErrorintrA DEFAULT_PORT) addr_pathfaddrports r _read_addrzNATSGatewayAPI._read_addrsI !>#C   i (Avvxx~~'' ( ( ( ( ( ( ( ( ( ( ( ( ( ( (       D  I&N,G(H(H I I  #D"""s4A3'A# A3#A''A3*A'+A33 B?BcK|j|jjrdStstdt j}||jz }||jkrtd|j|z dd||_|d{V| }tj d|j } t|5}|}dddn #1swxYwYt!jd|||jdt&d{V|_t(d |dS#t,$r}td ||d}~wwxYw) Nznats-py is not installedzNATS reconnect backoff (z.1fz s remaining)I360_NATS_TOKEN_PATHznats://r)rconnect_timeoutmax_reconnect_attemptserror_cbzConnected to NATS at %szFailed to connect to NATS: )r is_connected _has_natsrr monotonicrMIN_RECONNECT_INTERVAL_closer%rrDEFAULT_TOKEN_PATHrrrnatsconnectCONNECT_TIMEOUTrr2r Exception)rnow since_lastr# token_pathr"rrs r _ensure_connectedz NATSGatewayAPI._ensure_connecteds  8 DH$9  F G*+EFF Fn455 3 3 3*P0:=OPPP &)"kkmm  Y5t7NOO  j!! )Q(( ) ) ) ) ) ) ) ) ) ) ) ) ) ) )!\ $   $ 4'(' DH KK14 8 8 8 8 8   *1a11  s=E'D8 EDE D AE E?'E::E?rNc K|d{Vd} |j}|D]\}} tj|}nC#tjt f$r*}td||dz }Yd}~Wd}~wwxYw| dst|d|dz }| d}|j |z} tj|| dfg} g} | r| \} } tj| }| rd| ind} || ||d{V}nY#t&t(f$rD}t+|t(r:|jt.krt1d | d | d |||jt2krt5| }|A| | t9|t;||dd fYd}~| p%t=j| }tC|D](\}}|d |}||d<| ||f)td| | t9|t9|Yd}~d}~wwxYwtEj dr't#d| |j$|j%| | r|xj&dz c_&tOtPr|xj)dz c_)| d\}}}}t*d| | dt9| tWd| Dd| D|j&|| nTtYj-i|d|it\dt^j01| d|dz }dS#t0$rU}td|t9|t9||z |ted|||d}~wtf$r^}|4d{Vtd|t9||ted|||d}~wwxYw)NrzSkipping malformed message: %sr-rr1r.z Nats-Msg-Id)rzsubject=z message_id=z: .zKSplitting oversized NATS message: subject=%s message_id=%s size=%d parts=%drz"Published to %s, stream=%s, seq=%sz~Dropping oversized NATS message: subject=%s message_id=%s parts=%d size=%d fragments=%s oversized_total=%d error=%s preview=%rc3$K|] \}}}}|V dSrFr)rBrsizes r rGz/NATSGatewayAPI.send_messages..`s*>>]QaD>>>>>>r cg|] \}}}}| Srr)rBfragrs r rpz0NATSGatewayAPI.send_messages..as ;;;-$1a;;;r zagent-nats-sendingrz$NN!)3w<<Q# O%HHHH$I W(=(=(G(G(I(I!,5V+<+<>>KE4+3(=(=e(=(=I1:D.#NND)+<====>#$LLKK !S)!TuW~~ @#JG g8@L++q0++!"ABB600A500+21:(Aq%LLB  <00G >>g>>>>>;;7;;;/"$4648V44*2 !)11&**\2J2JKKKQ ow w r    NNNH H )    +*q**#    ++--        NN>H      +2q22#  s!PAPB- B PBCPE97P9K BK &P,BK P KEP S#AQ33 SASScK|jF |jd{Vn#t$rYnwxYwd|_dS#d|_wxYwdSrF)rcloser4rs r r/zNATSGatewayAPI._closes 8  hnn&&&&&&&&&&     4 s!+A 8A8A A c>K|d{VdSrF)r/rs r r`zNATSGatewayAPI.closes,kkmmr )rrrr9rFr r0rr3r.rrr staticmethodr%r8rcr r rr r/r`rr r rrs )L97O%%%  ##\#&###JYDue|1D,EY$YYYYv    r r)XrrGrMrrr urllib.errorrr1 nats.errorsnats.js.errorsr,errorsMaxPayloadErrorrrTrr ImportErrorr4urllib.requestabcrrloggingrtypingrrrdefence360agent.api.serverrrrr defence360agent.contracts.configr"defence360agent.contracts.messagesrrdefence360agent.internalsr'defence360agent.internals.feature_flagsrr&defence360agent.internals.global_scoperdefence360agent.internals.iaidrr2defence360agent.internals.message_status_publisherr r!!defence360agent.utils.async_utilsr"defence360agent.utils.jsonr#rr2rrSr1r&rr)rHrAr6rKrLr8 frozensetr^_MAX_SPLIT_DEPTHrIrPr\boolr`rfrdrbrrrrrrrr r rzs   KKKI;6GN+MMI     y     ######## 211111EEEEEEEE222222544444NMMMMMMM::::::888888 8  CEESUU ! !Ct34*OOOOO)OOOi9-..  s     $"IIIII&&&8,<,,t,,,,^T0!Y!4!!!!$$$$$c$$$44848484848'484848n;I;I;I;I;I.;I;I;I|{{{{{{{{{{s+A #A0/A0defence360agent/api/server/analyst_cleanup.py0000644000000000000000000001700200000000000016361 0ustar import http.client import json import urllib.error import urllib.request import logging from datetime import datetime, timedelta from defence360agent.api.server import API from defence360agent.contracts.config import ANTIVIRUS_MODE from defence360agent.internals.iaid import ( IndependentAgentIDAPI, IAIDTokenError, ) from defence360agent.rpc_tools.utils import run_in_executor_decorator from defence360agent.utils.support import parse_params logger = logging.getLogger(__name__) CACHE_TTL = timedelta(minutes=10) NO_AGENT_TOKEN = "no_agent_token" def _json_object(response): try: body = json.load(response) except (ValueError, OSError, http.client.HTTPException) as e: logger.warning("Cannot decode API response body: %s", e) return {} if isinstance(body, dict): return body logger.warning( "API response body is %s, not an object", type(body).__name__ ) return {} class AnalystCleanupAPI(API): CLEANUP_ALLOWED_URL_TEMPLATE = ( "{base}/api/analyst-assisted-cleanup/is-allowed" ) SHOW_MANY_URL_TEMPLATE = "{base}/api/analyst-assisted-cleanup/tickets" IS_REGISTERED_URL_TEMPLATE = ( "{base}/api/analyst-assisted-cleanup/is-registered" ) CREATE_TICKET_URL_TEMPLATE = ( "{base}/api/analyst-assisted-cleanup/create-ticket" ) # Cache for the cleanup allowed check _cache = { "result": None, "timestamp": datetime.min, # Initialize with minimum datetime } @classmethod async def check_cleanup_allowed(cls): """Check if analyst cleanup is allowed for this installation""" current_time = datetime.now() if ( cls._cache["result"] is not None and current_time - cls._cache["timestamp"] < CACHE_TTL ): return cls._cache["result"] try: request = urllib.request.Request( cls.CLEANUP_ALLOWED_URL_TEMPLATE.format(base=cls._BASE_URL), headers={"X-Auth": await IndependentAgentIDAPI.get_token()}, method="GET", ) except IAIDTokenError: return False else: result = await cls._check_allowed(request) cls._cache["result"] = result cls._cache["timestamp"] = datetime.now() return result @classmethod @run_in_executor_decorator def _check_allowed(cls, request): """Execute the actual request in executor""" try: result = cls.request(request) return result.get("result", False) except Exception as e: logger.error("Failed to check cleanup permission: %s", e) # NOTE: # If the API returns an error, the request should be allowed # (to prevent extra sales trips due to API instability). # Ref: https://cloudlinux.slite.com/app/docs/Fhb2ASESxb9111 return True @classmethod async def get_tickets(cls, ids: [str]) -> [dict]: """ Retrieve tickets from Zendesk API using the show_many endpoint Args: ids (list or str): List of ticket IDs or comma-separated string of IDs Returns: list: List of dictionaries with 'id', 'status', and 'updated_at' fields """ # Convert list of ids to comma-separated string if necessary if isinstance(ids, list): ids_str = ",".join(str(_id) for _id in ids) else: ids_str = str(ids) # Construct URL for the show_many endpoint url = cls.SHOW_MANY_URL_TEMPLATE.format(base=cls._BASE_URL) params = {"ids": ids_str} try: request = urllib.request.Request( parse_params(params, url), headers={"X-Auth": await IndependentAgentIDAPI.get_token()}, method="GET", ) except IAIDTokenError as e: logger.error(f"Failed to get IAID token for tickets: {e}") raise return await cls._execute_get_tickets(request) @classmethod @run_in_executor_decorator def _execute_get_tickets(cls, request): """Execute the actual get_tickets request in executor""" simplified_tickets = [] try: result = cls.request(request) # Extract only the required fields from each ticket for ticket in result.get("tickets", []): simplified_tickets.append( { "id": ticket.get("id"), "status": ticket.get("status"), "updated_at": ticket.get("updated_at"), } ) return simplified_tickets except Exception as e: logger.error(f"Failed to get tickets: {e}") finally: return simplified_tickets @classmethod async def check_registered(cls, email): """Check if email is registered in Zendesk""" try: request = urllib.request.Request( cls.IS_REGISTERED_URL_TEMPLATE.format(base=cls._BASE_URL), headers={ "X-Auth": await IndependentAgentIDAPI.get_token(), "Content-Type": "application/json", }, data=json.dumps({"customer_email": email}).encode(), method="POST", ) except IAIDTokenError: logger.error("Got IAIDTokenError") return {} else: result = await cls._register_status(request) return result @classmethod @run_in_executor_decorator def _register_status(cls, request): """Execute the actual request in executor""" try: result = cls.request(request) return result except Exception as e: logger.error("Failed to check email registration: %s", e) return {} @classmethod async def create_ticket(cls, email, subject, description): """Ask the backend to open a support ticket. Return an (HTTP status, response body) pair; the status is None when the backend could not be reached at all. """ try: token = await IndependentAgentIDAPI.get_token() except IAIDTokenError: return None, {"message": NO_AGENT_TOKEN} request = urllib.request.Request( cls.CREATE_TICKET_URL_TEMPLATE.format(base=cls._BASE_URL), headers={ "X-Auth": token, "Content-Type": "application/json", }, data=json.dumps( { "email": email, "subject": subject, "description": description, "product": ( "pr_imunify_av" if ANTIVIRUS_MODE else "pr_im360" ), } ).encode(), method="POST", ) return await cls._send_create_ticket(request) @classmethod @run_in_executor_decorator def _send_create_ticket(cls, request): """Execute the actual request in executor""" try: with urllib.request.urlopen( request, timeout=cls._SOCKET_TIMEOUT ) as response: return response.status, _json_object(response) except urllib.error.HTTPError as e: return e.code, _json_object(e) if e.fp is not None else {} except Exception as e: logger.warning("Failed to reach create-ticket endpoint: %s", e) return None, {} defence360agent/api/server/cleanup_revert.py0000644000000000000000000000146100000000000016217 0ustar import logging from urllib.parse import urljoin from urllib.request import Request from defence360agent.api.server import API, APIError from defence360agent.internals.iaid import ( IndependentAgentIDAPI, IAIDTokenError, ) logger = logging.getLogger(__name__) class CleanupRevertAPI(API): URL = urljoin(API._BASE_URL, "/api/cleanup/revert") @classmethod async def paths(cls): try: token = await IndependentAgentIDAPI.get_token() except IAIDTokenError: return [] request = Request(cls.URL, headers={"X-Auth": token}) try: result = await cls.async_request(request) except APIError as exc: logger.warning("Failed to fetch cleanup revert data: %s", exc) return [] return result["paths"] defence360agent/api/server/events.py0000644000000000000000000000351100000000000014503 0ustar import urllib.request import logging import datetime from defence360agent.api.server import API from defence360agent.internals.iaid import IndependentAgentIDAPI from defence360agent.rpc_tools.utils import run_in_executor_decorator logger = logging.getLogger(__name__) class EventsAPI(API): ADVICES_API_URL_TEMPLATE = ( "{base}/api/dashboard/events?dashboard=false&" "popup=true¬_snoozed_at={not_snoozed_at}" ) NOTIFICATIONS_API_URL_TEMPLATE = ( "{base}/api/dashboard/v2/events?notification=1&enduser=true" ) SMART_ADVICE_API_URL_TEMPLATE = ( "{base}/api/dashboard/v2/events?smartadvice=true" ) @classmethod @run_in_executor_decorator def advices(cls): request = urllib.request.Request( cls.ADVICES_API_URL_TEMPLATE.format( base=cls._BASE_URL, not_snoozed_at=int(datetime.datetime.now().timestamp()), ), method="GET", ) result = cls.request(request) return result["result"] @classmethod async def notification(cls): request = urllib.request.Request( cls.NOTIFICATIONS_API_URL_TEMPLATE.format(base=cls._BASE_URL), method="GET", headers={"X-Auth": await IndependentAgentIDAPI.get_token()}, ) return await cls._send_notifications(request) @classmethod async def smart_advices(cls): request = urllib.request.Request( cls.SMART_ADVICE_API_URL_TEMPLATE.format(base=cls._BASE_URL), method="GET", headers={"X-Auth": await IndependentAgentIDAPI.get_token()}, ) return cls.request(request)["result"] @classmethod @run_in_executor_decorator def _send_notifications(cls, request): result = cls.request(request) return result["result"] defence360agent/api/server/reputation.py0000644000000000000000000000436100000000000015375 0ustar import json import urllib.error import urllib.request import urllib.parse import asyncio from typing import List import time import logging from defence360agent.utils import retry_on, split_for_chunk from defence360agent.api.server import API, APIError logger = logging.getLogger(__name__) class ReputationAPI(API): REQUEST_URL = "/api/reputation/check" RESULT_URL = "/api/reputation/result" # during stress tests 'Request Entity Too Large' error has been caught, # in request size somewhere between 800000 and 900000 bytes # max domain length - 255, 800000 / 255 = 3137 # 3000 is the nearest 'round' number CHUNK_SIZE = 3000 WAIT_BEFORE_RETRY = 5 WAIT_FOR_RESULT = 1200 _SOCKET_TIMEOUT = 60 @classmethod async def check(cls, domains: List[str]) -> List[dict]: logger.info("DomainListRequest domains: %s", domains) loop = asyncio.get_event_loop() return await loop.run_in_executor(None, cls._check, domains) @classmethod def _check(cls, domains: List[str]) -> List[dict]: result_list = [] for chunk in split_for_chunk(domains, cls.CHUNK_SIZE): result = cls._check_chunk(chunk) next_chunk = cls._get_result(result["result_id"]) result_list += next_chunk return result_list @classmethod @retry_on(APIError, timeout=WAIT_FOR_RESULT) def _check_chunk(cls, chunk) -> dict: check_request = urllib.request.Request( cls._BASE_URL + cls.REQUEST_URL, method="POST", headers={"Content-Type": "application/json"}, data=json.dumps(dict(domains=chunk)).encode(), ) return cls.request(check_request) @classmethod @retry_on(APIError, timeout=WAIT_FOR_RESULT) def _get_result(cls, result_id: str): data = dict(result_id=result_id) url = "{}?{}".format( cls._BASE_URL + cls.RESULT_URL, urllib.parse.urlencode(data) ) request = urllib.request.Request(url) response = cls.request(request) result = response["result"] if result is None: # time inside sync executor time.sleep(cls.WAIT_BEFORE_RETRY) raise APIError("Response not ready yet") return result defence360agent/api/server/send_message.py0000644000000000000000000006102700000000000015642 0ustar import base64 import collections import hashlib import json import os import time import urllib.error try: import nats import nats.errors import nats.js.errors _has_nats = True _NATSMaxPayloadError = nats.errors.MaxPayloadError _NATSAPIError = nats.js.errors.APIError except ImportError: _has_nats = False class _NATSMaxPayloadError(Exception): pass class _NATSAPIError(Exception): err_code = None import urllib.request from abc import ABC, abstractmethod from logging import getLogger from typing import Optional import asyncio import uuid from defence360agent.api.server import ( API, APIError, APITokenError, FGWSendMessgeException, NATSSendMessageException, ) from defence360agent.contracts.config import Core from defence360agent.contracts.messages import estimate_size, Message from defence360agent.internals import delivery_ack from defence360agent.internals.feature_flags import ( MESSAGE_LOSS_OBSERVABILITY_FLAG, is_enabled, ) from defence360agent.internals.global_scope import g from defence360agent.internals.iaid import ( IndependentAgentIDAPI, IAIDTokenError, ) from defence360agent.internals.message_status_publisher import Gen, publisher from defence360agent.utils.async_utils import AsyncIterate from defence360agent.utils.json import ServerJSONEncoder logger = getLogger(__name__) _reporter_gen_fgw = Gen() _reporter_gen_nats = Gen() _method_missing_dropped_total = 0 _method_missing_dropped_delta = 0 def pop_method_missing_dropped() -> int: """Method-less drops since the last call, then reset (delta).""" global _method_missing_dropped_delta value, _method_missing_dropped_delta = _method_missing_dropped_delta, 0 return value def _drop_method_less(message: dict, sink: str) -> None: """Count and log a message no sink can route.""" # Key names only, except plugin_id: it names the producer, not the payload. global _method_missing_dropped_total, _method_missing_dropped_delta _method_missing_dropped_total += 1 _method_missing_dropped_delta += 1 logger.error( "Dropping message without a method: sink=%s message_id=%s" " plugin_id=%s timestamp=%s keys=%s dropped_total=%d", sink, message.get("message_id"), message.get("plugin_id"), message.get("timestamp"), sorted(message), _method_missing_dropped_total, ) # Returned for both "maximum messages exceeded" and "maximum bytes exceeded" # once the stream is full; reaches the client only because the stream discards # new rather than old messages. _JS_ERR_STREAM_FULL = 10077 # The stream's own MaxMsgSize rejection, distinct from the client-side # MaxPayloadError checked against the server's max_payload. Both caps are 10MB # today, so this only fires if MaxMsgSize is lowered below max_payload. _JS_ERR_MSG_TOO_LARGE = 10054 class _StreamFull(Exception): """Stream is at capacity: re-queue the rest, the connection is healthy.""" # Keys the receiving side indexes a fragment on. A field carrying one of them # identifies the message instead of holding its payload: bisecting it strands # fragments the store path cannot key, so it is copied into every fragment. _INDEX_KEYS = frozenset({"scanid", "scan_id"}) # Single-element descents before a message is called irreducible: json.loads # accepts nesting deeper than this mutual recursion can safely walk. _MAX_SPLIT_DEPTH = 32 def _element_ids(container) -> set: """Strings a sibling map could be keyed on: a dict's own keys, or the scalar values carried by a list's elements.""" if isinstance(container, dict): return {key for key in container if isinstance(key, str)} ids = set() for element in container: if isinstance(element, str): ids.add(element) elif isinstance(element, dict): ids.update( value for value in element.values() if isinstance(value, str) ) return ids def _paired_maps(fields: dict) -> dict: """Sibling dicts keyed entirely by a field's elements, which have to follow those records rather than be bisected away from them.""" ids = {name: _element_ids(value) for name, value in fields.items()} return { name: { other for other, value in fields.items() if other != name and isinstance(value, dict) and value and set(value) <= ids[name] } for name in fields } def _carries_index_key(value) -> bool: return isinstance(value, dict) and not _INDEX_KEYS.isdisjoint(value) def _split_element(element, depth): if depth <= 0: return None if isinstance(element, dict): return _split_largest_field(element, depth - 1) if isinstance(element, list): return _split_container(element, depth - 1) return None def _split_container(value, depth): """Bisect a list or dict, descending into a lone element so a single oversized record can still be split within itself.""" if isinstance(value, dict): keys = list(value) if len(keys) > 1: mid = len(keys) // 2 return [ {key: value[key] for key in keys[:mid]}, {key: value[key] for key in keys[mid:]}, ] if not keys: return None inner = _split_element(value[keys[0]], depth) if inner is None: return None return [{keys[0]: half} for half in inner] if len(value) > 1: mid = len(value) // 2 return [value[:mid], value[mid:]] if not value: return None inner = _split_element(value[0], depth) if inner is None: return None return [[half] for half in inner] def _split_largest_field(item: dict, depth=_MAX_SPLIT_DEPTH): """Bisect the heaviest payload field, while index-carrying fields and maps paired with another field's records ride along instead of being split.""" # ponytail: pairing is inferred from depth-1 scalars, not read from the # producer's declared batch field, so a nested or renamed join key quietly # degrades to duplicating records; byte-bound the producers to retire this. fields = { name: value for name, value in item.items() if isinstance(value, (list, dict)) } paired = _paired_maps(fields) followers = {name for maps in paired.values() for name in maps} candidates = [ name for name, value in fields.items() if name not in followers and not _carries_index_key(value) ] def weight(name): return estimate_size(fields[name]) + sum( estimate_size(fields[other]) for other in paired[name] ) # Heaviest first, but fall through to the next candidate when the heaviest # cannot be bisected: sorted() is stable, so a tie keeps insertion order # and a re-split reproduces the same fragments and dedup ids. for field in sorted(candidates, key=weight, reverse=True): halves = _split_container(fields[field], depth) if halves is not None: break else: return None parts = [] for half in halves: kept = _element_ids(half) part = {**item, field: half} for name in paired[field]: part[name] = { key: value for key, value in fields[name].items() if key in kept } parts.append(part) return parts def _split_oversized(loaded: dict): """Split an oversized message into smaller parts. Returns a list of parts, or None when the message carries a single irreducible record.""" items = loaded.get("items") if isinstance(items, list) and len(items) > 1: mid = len(items) // 2 return [ {**loaded, "items": items[:mid]}, {**loaded, "items": items[mid:]}, ] single = items[0] if isinstance(items, list) and len(items) == 1 else None if isinstance(single, dict): try: halves = _split_largest_field(single) except RecursionError: # estimate_size walks the item too, and json.loads accepts nesting # deeper than it can measure. Unsplittable beats re-queueing the # batch forever on a message no depth cap of ours can save. return None if halves is not None: return [{**loaded, "items": [half]} for half in halves] return None async def _nats_error_cb(ex: Exception) -> None: """Downgrade nats-py internal errors to DEBUG. Transient errors (ConnectionRefused, AuthorizationViolation) are expected during agent restarts. Our code already logs a WARNING with context, so the nats-py default ERROR + traceback is noise. """ logger.debug("nats: %s", ex) class BaseSendMessageAPI(API, ABC): URL = "/api/v2/send-message/{method}" @abstractmethod async def _send_request(self, message_method, headers, post_data) -> dict: pass # pragma: no cover def check_response(self, result: dict) -> None: if "status" not in result: raise APIError("unexpected server response: {!r}".format(result)) if result["status"] != "ok": raise APIError("server error: {}".format(result.get("msg"))) async def send_data(self, method: str, post_data: bytes) -> None: try: token = await IndependentAgentIDAPI.get_token() except IAIDTokenError as e: raise APITokenError(f"IAID token error occurred {e}") headers = { "Content-Type": "application/json", "X-Auth": token, } result = await self._send_request(method, headers, post_data) self.check_response(result) class SendMessageAPI(BaseSendMessageAPI): _SOCKET_TIMEOUT = Core.DEFAULT_SOCKET_TIMEOUT def __init__(self, rpm_ver: str, base_url: str = None, executor=None): self._executor = executor self.rpm_ver = rpm_ver self.product_name = "" self.server_id = None # type: Optional[str] self.license = {} # type: dict if base_url: self.base_url = base_url else: self.base_url = self._BASE_URL def set_product_name(self, product_name: str) -> None: self.product_name = product_name def set_server_id(self, server_id: Optional[str]) -> None: self.server_id = server_id def set_license(self, license: dict) -> None: self.license = license async def _send_request(self, message_method, headers, post_data): request = urllib.request.Request( self.base_url + self.URL.format(method=message_method), data=post_data, headers=headers, method="POST", ) return await self.async_request(request, executor=self._executor) async def send_message(self, message: Message) -> None: # Return, don't raise: raising re-queues the entry at the backlog head. if not message.get("method"): _drop_method_less(message, "http") return # add message handling time if it does not exist, so that # the server does not depend on the time it was received if "timestamp" not in message: message["timestamp"] = time.time() if "message_id" not in message: message["message_id"] = uuid.uuid4().hex data2send = { "payload": message.payload, "rpm_ver": self.rpm_ver, "message_id": message.message_id, "server_id": self.server_id, "name": self.product_name, } post_data = json.dumps(data2send, cls=ServerJSONEncoder).encode() await self.send_data(message.method, post_data) class FileBasedGatewayAPI(SendMessageAPI): async def _prepare_message(self, message, semaphore) -> dict: async with semaphore: loaded = await asyncio.to_thread(json.loads, message) return { "method": loaded["method"], "data": {k: v for k, v in loaded.items() if k != "method"}, } async def send_messages(self, messages: list[tuple[float, bytes]]) -> None: max_threads = 5 semaphore = asyncio.Semaphore(max_threads) tasks = [ self._prepare_message(msg, semaphore) async for _, msg in AsyncIterate(messages) ] prepared_messages = await asyncio.gather(*tasks) for msg in prepared_messages: flat = {**msg.get("data", {}), "method": msg.get("method", "")} publisher.report( flat, _reporter_gen_fgw, stage="agent-fgw-sending" ) dumped_messages = await asyncio.to_thread( json.dumps, prepared_messages ) bin_file_path = os.getenv( "I360_MESSAGE_GATEWAY_BIN_PATH", "/usr/libexec/" ) bin_file = os.path.join(bin_file_path, "imunify-message-gateway") command = [ bin_file, "send-many", "--producer=i360-agent-non-resident", ] process = await asyncio.create_subprocess_exec( *command, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) b64data = base64.b64encode(dumped_messages.encode()) stdout, stderr = await process.communicate(input=b64data) if g.get("DEBUG"): logger.info( "Message sent to fgw: %s %s %s", len(messages), stdout, stderr ) if process.returncode != 0: logger.error(f"Error sending message: {stderr.decode()}") raise FGWSendMessgeException( str(f"Error sending message: {stderr.decode()}") ) for msg in prepared_messages: delivery_ack.registry.confirm(msg["data"].get("message_id")) class NATSGatewayAPI: """Publishes messages to the embedded NATS server via localhost TCP. Connects to nats://127.0.0.1: with an auth token read from a file written by the resident-agent on startup. """ NATS_SUBJECT_PREFIX = "imunify.api." DEFAULT_PORT = 44222 DEFAULT_TOKEN_PATH = "/var/run/imunify360/nats.token" DEFAULT_ADDR_PATH = "/var/run/imunify360/nats.addr" CONNECT_TIMEOUT = 5 MIN_RECONNECT_INTERVAL = 5 def __init__(self): self._nc = None self._last_connect_attempt = 0 self._oversized_dropped = 0 self._oversized_rejected = 0 def pop_oversized_rejected(self) -> int: """Oversized rejections since the last call, then reset (delta).""" value, self._oversized_rejected = self._oversized_rejected, 0 return value @staticmethod def _read_addr(): """Read NATS listen address from addr file, fall back to env/default.""" addr_path = os.getenv( "I360_NATS_ADDR_PATH", NATSGatewayAPI.DEFAULT_ADDR_PATH ) try: with open(addr_path) as f: addr = f.read().strip() if addr: return addr except OSError: pass # Fallback: env var / hardcoded default (for upgrades where # the resident-agent hasn't written the addr file yet) port = int( os.getenv("I360_NATS_PORT", str(NATSGatewayAPI.DEFAULT_PORT)) ) return f"127.0.0.1:{port}" async def _ensure_connected(self): if self._nc is not None and self._nc.is_connected: return if not _has_nats: raise NATSSendMessageException("nats-py is not installed") now = time.monotonic() since_last = now - self._last_connect_attempt if since_last < self.MIN_RECONNECT_INTERVAL: raise NATSSendMessageException( "NATS reconnect backoff" f" ({self.MIN_RECONNECT_INTERVAL - since_last:.1f}s remaining)" ) self._last_connect_attempt = now # Clean up stale connection before reconnecting await self._close() addr = self._read_addr() token_path = os.getenv("I360_NATS_TOKEN_PATH", self.DEFAULT_TOKEN_PATH) try: with open(token_path) as f: token = f.read().strip() self._nc = await nats.connect( f"nats://{addr}", token=token, connect_timeout=self.CONNECT_TIMEOUT, max_reconnect_attempts=0, error_cb=_nats_error_cb, ) logger.info("Connected to NATS at %s", addr) except Exception as e: raise NATSSendMessageException( f"Failed to connect to NATS: {e}" ) from e async def send_messages(self, messages: list[tuple[float, bytes]]) -> None: await self._ensure_connected() published = 0 try: js = self._nc.jetstream() for _, msg_bytes in messages: try: loaded = json.loads(msg_bytes) except (json.JSONDecodeError, UnicodeDecodeError) as e: logger.warning("Skipping malformed message: %s", e) published += 1 # count as handled, not re-queued continue if not loaded.get("method"): _drop_method_less(loaded, "nats") published += 1 # count as handled, not re-queued continue method = loaded.pop("method") subject = self.NATS_SUBJECT_PREFIX + method # (part, dedup_id) pairs. dedup_id pins each fragment's # Nats-Msg-Id deterministically: when a message is split and a # later fragment fails with a non-payload error, the wrapper # re-queues the whole original; re-splitting reproduces the # same fragments and ids, so JetStream de-duplicates the ones # already delivered instead of duplicating them. pending = collections.deque( [(loaded, loaded.get("message_id"))] ) # (id, size, error, preview) per irreducible part. str(e), not # the exception: its traceback would pin this frame's payload # and part until the message is done. dropped = [] while pending: part, dedup_id = pending.popleft() payload = json.dumps(part).encode() headers = {"Nats-Msg-Id": dedup_id} if dedup_id else None try: ack = await js.publish( subject, payload, headers=headers ) except (_NATSMaxPayloadError, _NATSAPIError) as e: if isinstance(e, _NATSAPIError): if e.err_code == _JS_ERR_STREAM_FULL: # Abort the batch so this message and the rest # are re-queued whole; already-published # fragments carry deterministic ids and are # de-duplicated on retry. raise _StreamFull( f"subject={subject}" f" message_id={dedup_id}: {e}" ) from e if e.err_code != _JS_ERR_MSG_TOO_LARGE: raise halves = _split_oversized(part) if halves is None: # A single record that alone exceeds the limit # cannot be delivered over NATS. The other # fragments of this message are still published; # only this irreducible record is dropped (loudly, # with a counter). It is intentionally counted as # handled rather than re-queued, otherwise it would # block the head of the queue forever. dropped.append( (dedup_id, len(payload), str(e), payload[:200]) ) continue base_key = ( dedup_id or hashlib.sha1(payload).hexdigest() ) for index, half in enumerate(halves): child_key = f"{base_key}.{index}" half["message_id"] = child_key pending.append((half, child_key)) logger.warning( "Splitting oversized NATS message: subject=%s" " message_id=%s size=%d parts=%d", subject, dedup_id, len(payload), len(halves), ) continue if g.get("DEBUG"): logger.debug( "Published to %s, stream=%s, seq=%s", subject, ack.stream, ack.seq, ) # One drop and one status report per logical message, not per # fragment: a split message's fragments share the parent's # reporter id, and counting each recursive attempt inflates # both the delivery-tracking cardinality and the drop metric. # A message with any dropped fragment is not reported at all: # it did not fully reach NATS, so tracking it as sent would # overstate delivery. if dropped: self._oversized_dropped += 1 if is_enabled(MESSAGE_LOSS_OBSERVABILITY_FLAG): self._oversized_rejected += 1 _, _, error, preview = dropped[0] logger.error( # size= is the bytes dropped across every part and is # parsed by the rpm-test that guards this path; keep # the field name if the format changes again. "Dropping oversized NATS message: subject=%s" " message_id=%s parts=%d size=%d fragments=%s" " oversized_total=%d error=%s preview=%r", subject, loaded.get("message_id"), len(dropped), sum(size for _, size, _, _ in dropped), [frag for frag, _, _, _ in dropped], self._oversized_dropped, error, preview, ) else: publisher.report( {**loaded, "method": method}, _reporter_gen_nats, stage="agent-nats-sending", ) delivery_ack.registry.confirm(loaded.get("message_id")) published += 1 except _StreamFull as e: # Keep the connection: it is healthy, and closing it would make # recovery wait out MIN_RECONNECT_INTERVAL as well. logger.warning( "NATS stream full, %d/%d messages published, %d re-queued: %s", published, len(messages), len(messages) - published, e, ) raise NATSSendMessageException( f"Stream at capacity: {e}", published=published, ) from e except Exception as e: await self._close() logger.warning( "NATS publish failed after %d/%d messages: %s", published, len(messages), e, ) raise NATSSendMessageException( f"Failed to publish messages: {e}", published=published, ) from e async def _close(self): if self._nc is not None: try: # close(), not drain(): drain PINGs the server we already # consider broken, stalls the send path on the flush timeout, # and on that timeout leaks the client with its read loop # alive. Unacked messages are re-queued, so nothing is lost. await self._nc.close() except Exception: pass finally: self._nc = None async def close(self): await self._close() defence360agent/application/0000755000000000000000000000000000000000000013051 5ustar defence360agent/application/__init__.py0000644000000000000000000000133600000000000015165 0ustar """This module conatins only global application class and object. Please, do not import any other modules there. """ class Application: """Store settings for different parts of application. SCHEMA_PATHS - additional paths to store RPC/CLI schemas VALIDATOR - SchemaValidator object MIDDLEWARE - dict with middleware to apply MIDDLEWARE_EXCLUDE - dict with middleware that should be excluded MODULES_WITH_MODELS - list of modules with models MIGRATIONS_DIRS - list of dirs with migrations """ SCHEMA_PATHS = None VALIDATOR = None MIDDLEWARE = None MIDDLEWARE_EXCLUDE = None MODULES_WITH_MODELS = [] MIGRATIONS_DIRS = [] MIGRATIONS_ATTACHED_DBS = [] app = Application() defence360agent/application/__pycache__/0000755000000000000000000000000000000000000015261 5ustar defence360agent/application/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000223100000000000022457 0ustar r_j8dZGddZeZdS)znThis module conatins only global application class and object. Please, do not import any other modules there. c.eZdZdZdZdZdZdZgZgZ gZ dS) ApplicationazStore settings for different parts of application. SCHEMA_PATHS - additional paths to store RPC/CLI schemas VALIDATOR - SchemaValidator object MIDDLEWARE - dict with middleware to apply MIDDLEWARE_EXCLUDE - dict with middleware that should be excluded MODULES_WITH_MODELS - list of modules with models MIGRATIONS_DIRS - list of dirs with migrations N) __name__ __module__ __qualname____doc__ SCHEMA_PATHS VALIDATOR MIDDLEWAREMIDDLEWARE_EXCLUDEMODULES_WITH_MODELSMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSY/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/__init__.pyrrsBLIJO rrN)rrapprrrrsH33!!!!!!!!(kmmrdefence360agent/application/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000223100000000000021520 0ustar r_j8dZGddZeZdS)znThis module conatins only global application class and object. Please, do not import any other modules there. c.eZdZdZdZdZdZdZgZgZ gZ dS) ApplicationazStore settings for different parts of application. SCHEMA_PATHS - additional paths to store RPC/CLI schemas VALIDATOR - SchemaValidator object MIDDLEWARE - dict with middleware to apply MIDDLEWARE_EXCLUDE - dict with middleware that should be excluded MODULES_WITH_MODELS - list of modules with models MIGRATIONS_DIRS - list of dirs with migrations N) __name__ __module__ __qualname____doc__ SCHEMA_PATHS VALIDATOR MIDDLEWAREMIDDLEWARE_EXCLUDEMODULES_WITH_MODELSMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSY/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/__init__.pyrrsBLIJO rrN)rrapprrrrsH33!!!!!!!!(kmmrdefence360agent/application/__pycache__/determine_hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000572300000000000025617 0ustar r_jdZddlZddlmZddlmZdZdZdZdZ ej e Z d e fd Zd Zd Zd ZdZdZdS)zg Determines hosting panel. This module has minimal dependencies and only imports required panel class. N) import_module)Pathz/usr/local/cpanel/cpanelz"/usr/local/directadmin/directadminz/usr/sbin/pleskz*/etc/sysconfig/imunify360/integration.conf root_modulecFtr&t|d}|Str&t|d}|St r&t|d}|Str&t|d}|S|dkr&t|d}| St|d}| S)Nz.subsys.panels.generic.panelz.subsys.panels.plesk.panelz.subsys.panels.cpanel.panelz .subsys.panels.directadmin.paneldefence360agentz.subsys.panels.no_cp.panel) is_generic_panel_installedr GenericPanelis_plesk_installedPleskis_cpanel_installedcPanelis_directadmin_installed DirectAdminNoCPNoControlPanel)rmodules h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/determine_hosting_panel.pyget_hosting_panelrs5"##+KKKLL""$$$   +IIIJJ||~~    +JJJKK}} ! # #  < < <  !!### ) ) )+IIIJJ{{}} kEEE F FF  " ""c*ttSN)_is_panel_installed CPANEL_FILErrr r 3s { + ++rc*ttSr)rDA_FILErrrrr7 w ' ''rc*ttSr)rGP_FILErrrrr;rrc*ttSr)r PLESK_FILErrrr r ?s z * **rcDt|Sr)ris_file) panel_files rrrCs    # # % %%r)__doc__logging importlibrpathlibrrrr!r getLogger__name__loggerstrrr rrr rrrrr-s######( .  6  8 $ $#3####D,,,((((((+++&&&&&rdefence360agent/application/__pycache__/determine_hosting_panel.cpython-311.pyc0000644000000000000000000000572300000000000024660 0ustar r_jdZddlZddlmZddlmZdZdZdZdZ ej e Z d e fd Zd Zd Zd ZdZdZdS)zg Determines hosting panel. This module has minimal dependencies and only imports required panel class. N) import_module)Pathz/usr/local/cpanel/cpanelz"/usr/local/directadmin/directadminz/usr/sbin/pleskz*/etc/sysconfig/imunify360/integration.conf root_modulecFtr&t|d}|Str&t|d}|St r&t|d}|Str&t|d}|S|dkr&t|d}| St|d}| S)Nz.subsys.panels.generic.panelz.subsys.panels.plesk.panelz.subsys.panels.cpanel.panelz .subsys.panels.directadmin.paneldefence360agentz.subsys.panels.no_cp.panel) is_generic_panel_installedr GenericPanelis_plesk_installedPleskis_cpanel_installedcPanelis_directadmin_installed DirectAdminNoCPNoControlPanel)rmodules h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/determine_hosting_panel.pyget_hosting_panelrs5"##+KKKLL""$$$   +IIIJJ||~~    +JJJKK}} ! # #  < < <  !!### ) ) )+IIIJJ{{}} kEEE F FF  " ""c*ttSN)_is_panel_installed CPANEL_FILErrr r 3s { + ++rc*ttSr)rDA_FILErrrrr7 w ' ''rc*ttSr)rGP_FILErrrrr;rrc*ttSr)r PLESK_FILErrrr r ?s z * **rcDt|Sr)ris_file) panel_files rrrCs    # # % %%r)__doc__logging importlibrpathlibrrrr!r getLogger__name__loggerstrrr rrr rrrrr-s######( .  6  8 $ $#3####D,,,((((((+++&&&&&rdefence360agent/application/__pycache__/settings.cpython-311.opt-1.pyc0000644000000000000000000000610600000000000022565 0ustar r_j dZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z dd lmZmZdd lmZdd lmZd d lmZdefdZeeeddejdddf dZdS)z"Set settings of application objectN)Path)files)tags)eula)g)simplification)SchemaValidatorvalidate_middleware)init_validator)update_wp_rules_on_sites)app is_updatedcDK|rtjd{VdSdS)N)rupdate)indexrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/settings.pyupdate_eula_datars<kmmFc *tjddkr dt_|t _||||\t _t _t _ t xj tgz c_ |rt xj |z c_ ttjj} t xj| dz gz c_|rt xj|z c_|rt xj|z c_|t%j|s`t$jt$jt.t$jt$jt2dSdS)NDEBUGtrueT migrations)osenvirongetrrr SCHEMA_PATHS VALIDATOR MIDDLEWAREMIDDLEWARE_EXCLUDEMODULES_WITH_MODELSrr__file__resolveparentMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSr configureIndexadd_hookEULArWP_RULESr ) r validator_clsvalidate_middleware_wrap schema_pathsmodels_modulesset_sentry_tagsmigration_dirsmigrations_attached_dbsresidentav_paths rr'r'sg z~~g&((#C3#9//2 >18nn$$&&-4GGl233. ~-? ##'>>##O O G UZ)9::: U^-EFFFFFGGr)__doc__rpathlibrdefence360agentrdefence360agent.applicationrdefence360agent.contractsr&defence360agent.internals.global_scoperdefence360agent.modelr"defence360agent.rpc_tools.validater r !defence360agent.simple_rpc.schemar defence360agent.wordpress.pluginr rboolrfillr'rrrCs>(( !!!!!!,,,,,,******444444000000=<<<<<EEEEEEd "!0I GGGGGGrdefence360agent/application/__pycache__/settings.cpython-311.pyc0000644000000000000000000000610600000000000021626 0ustar r_j dZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z dd lmZmZdd lmZdd lmZd d lmZdefdZeeeddejdddf dZdS)z"Set settings of application objectN)Path)files)tags)eula)g)simplification)SchemaValidatorvalidate_middleware)init_validator)update_wp_rules_on_sites)app is_updatedcDK|rtjd{VdSdS)N)rupdate)indexrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/settings.pyupdate_eula_datars<kmmFc *tjddkr dt_|t _||||\t _t _t _ t xj tgz c_ |rt xj |z c_ ttjj} t xj| dz gz c_|rt xj|z c_|rt xj|z c_|t%j|s`t$jt$jt.t$jt$jt2dSdS)NDEBUGtrueT migrations)osenvirongetrrr SCHEMA_PATHS VALIDATOR MIDDLEWAREMIDDLEWARE_EXCLUDEMODULES_WITH_MODELSrr__file__resolveparentMIGRATIONS_DIRSMIGRATIONS_ATTACHED_DBSr configureIndexadd_hookEULArWP_RULESr ) r validator_clsvalidate_middleware_wrap schema_pathsmodels_modulesset_sentry_tagsmigration_dirsmigrations_attached_dbsresidentav_paths rr'r'sg z~~g&((#C3#9//2 >18nn$$&&-4GGl233. ~-? ##'>>##O O G UZ)9::: U^-EFFFFFGGr)__doc__rpathlibrdefence360agentrdefence360agent.applicationrdefence360agent.contractsr&defence360agent.internals.global_scoperdefence360agent.modelr"defence360agent.rpc_tools.validater r !defence360agent.simple_rpc.schemar defence360agent.wordpress.pluginr rboolrfillr'rrrCs>(( !!!!!!,,,,,,******444444000000=<<<<<EEEEEEd "!0I GGGGGGrdefence360agent/application/__pycache__/tags.cpython-311.opt-1.pyc0000644000000000000000000001361600000000000021667 0ustar r_jL ddlZddlZddlZddlmZddlmZddlmZ ddl m Z ddl m Z ddlmZddlmZmZdd lmZejeZed Zd Zd Zd ZdddZdS)N)Path)sentry)Core) LicenseCLN)IndependentAgentIDAPI) hosting_panel)stub_unexpected_error is_root_user) IPEchoAPIz/var/imunify360/.sentry_tagscrttjtjdSN)SENTRY_TAGS_CACHE_PATH write_textjsondumpsr_TAGSU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/tags.pydump_sentry_tagsrs(%%dj&>&>?????rcftr tjtt _dS#tjtf$r+}t dt|Yd}~nd}~wt$rYnwxYwtddS)Nz%Sentry cache file %s is malformed: %sF)dump) rexistsrloads read_textrrJSONDecodeErrorFileNotFoundErrorloggerwarningPermissionErrorfill)es r cached_fillr#s$$&&  :&<&F&F&H&HIIFL F$&78    NN7&             D es5AB(!B BBcd}d}dtfddtfdtfd}|}tj||dkrtj|dStj|dS)N PRIMARY_IDSCSF_COOPreturnc tjddgdtjdS#ttjtjf$rYdSwxYw)Nz firewall-cmdz--state)timeoutstderrTF) subprocess check_outputDEVNULLIOErrorCalledProcessErrorTimeoutExpiredrrr_is_firewalld_runningz3_set_additional_tags.._is_firewalld_running.sn   #+!)     4   )  %     55  s#'!A  A c tjddgtj}n#ttjf$rYdSwxYwd|vod|vS)Nz /usr/sbin/csfz--status)r+Fshave been disableds/You have an unresolved error when starting csf:)r,r-r.rr0)outs r_is_csf_runningz-_set_additional_tags.._is_csf_running=ss ) *-j6HCC":#@A   55 %S0 >c I s "%??c8rdSrdSdS)Ncsf firewalldiptablesr)r5r2sr_get_current_firewallz3_set_additional_tags.._get_current_firewallHs3 ?   5 " " ;zrr7)boolr rset_firewall_type set_strategy)PRIMARY_IDS_STRATEGYCSF_COOP_STRATEGYr:fwr5r2s @@r_set_additional_tagsrA*s(" 4      T        B R    U{{-.....011111rTr'ctd}tjtjtjtjtjtjtj tj tsdStj tjtjt!jtjtt&jtj|tjt/|rt1dSdS)Nc2tjjSr )r HostingPanelNAMErrr_get_hosting_panelz fill.._get_hosting_panelZs)++00r)r rset_av_versionConfig AV_VERSIONset_core_version CORE_VERSION set_versionVERSIONset_product_namerget_product_namer set_server_id get_server_idset_iaidrget_iaidset_ipr server_ipset_hosting_panel set_test_envrAr)rrFs rr!r!Ys7111 &+,,, F/000 v~&&& J799::: >> 133444 O)244555 M<' (;<<>>??? //11222  r)T)r'N)rloggingr,pathlibrdefence360agent.contractsr defence360agent.contracts.configrrH!defence360agent.contracts.licenserdefence360agent.internals.iaidrdefence360agent.subsys.panelsrdefence360agent.utilsr r defence360agent.utils.ipechor getLogger__name__rrrr#rAr!rrrrcsB ,,,,,,;;;;;;888888@@@@@@777777322222  8 $ $<==@@@    ,2,2,2^rdefence360agent/application/__pycache__/tags.cpython-311.pyc0000644000000000000000000001361600000000000020730 0ustar r_jL ddlZddlZddlZddlmZddlmZddlmZ ddl m Z ddl m Z ddlmZddlmZmZdd lmZejeZed Zd Zd Zd ZdddZdS)N)Path)sentry)Core) LicenseCLN)IndependentAgentIDAPI) hosting_panel)stub_unexpected_error is_root_user) IPEchoAPIz/var/imunify360/.sentry_tagscrttjtjdSN)SENTRY_TAGS_CACHE_PATH write_textjsondumpsr_TAGSU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/application/tags.pydump_sentry_tagsrs(%%dj&>&>?????rcftr tjtt _dS#tjtf$r+}t dt|Yd}~nd}~wt$rYnwxYwtddS)Nz%Sentry cache file %s is malformed: %sF)dump) rexistsrloads read_textrrJSONDecodeErrorFileNotFoundErrorloggerwarningPermissionErrorfill)es r cached_fillr#s$$&&  :&<&F&F&H&HIIFL F$&78    NN7&             D es5AB(!B BBcd}d}dtfddtfdtfd}|}tj||dkrtj|dStj|dS)N PRIMARY_IDSCSF_COOPreturnc tjddgdtjdS#ttjtjf$rYdSwxYw)Nz firewall-cmdz--state)timeoutstderrTF) subprocess check_outputDEVNULLIOErrorCalledProcessErrorTimeoutExpiredrrr_is_firewalld_runningz3_set_additional_tags.._is_firewalld_running.sn   #+!)     4   )  %     55  s#'!A  A c tjddgtj}n#ttjf$rYdSwxYwd|vod|vS)Nz /usr/sbin/csfz--status)r+Fshave been disableds/You have an unresolved error when starting csf:)r,r-r.rr0)outs r_is_csf_runningz-_set_additional_tags.._is_csf_running=ss ) *-j6HCC":#@A   55 %S0 >c I s "%??c8rdSrdSdS)Ncsf firewalldiptablesr)r5r2sr_get_current_firewallz3_set_additional_tags.._get_current_firewallHs3 ?   5 " " ;zrr7)boolr rset_firewall_type set_strategy)PRIMARY_IDS_STRATEGYCSF_COOP_STRATEGYr:fwr5r2s @@r_set_additional_tagsrA*s(" 4      T        B R    U{{-.....011111rTr'ctd}tjtjtjtjtjtjtj tj tsdStj tjtjt!jtjtt&jtj|tjt/|rt1dSdS)Nc2tjjSr )r HostingPanelNAMErrr_get_hosting_panelz fill.._get_hosting_panelZs)++00r)r rset_av_versionConfig AV_VERSIONset_core_version CORE_VERSION set_versionVERSIONset_product_namerget_product_namer set_server_id get_server_idset_iaidrget_iaidset_ipr server_ipset_hosting_panel set_test_envrAr)rrFs rr!r!Ys7111 &+,,, F/000 v~&&& J799::: >> 133444 O)244555 M<' (;<<>>??? //11222  r)T)r'N)rloggingr,pathlibrdefence360agent.contractsr defence360agent.contracts.configrrH!defence360agent.contracts.licenserdefence360agent.internals.iaidrdefence360agent.subsys.panelsrdefence360agent.utilsr r defence360agent.utils.ipechor getLogger__name__rrrr#rAr!rrrrcsB ,,,,,,;;;;;;888888@@@@@@777777322222  8 $ $<==@@@    ,2,2,2^rdefence360agent/application/determine_hosting_panel.py0000644000000000000000000000400400000000000020307 0ustar """ Determines hosting panel. This module has minimal dependencies and only imports required panel class. """ import logging from importlib import import_module from pathlib import Path CPANEL_FILE = "/usr/local/cpanel/cpanel" DA_FILE = "/usr/local/directadmin/directadmin" PLESK_FILE = "/usr/sbin/plesk" GP_FILE = "/etc/sysconfig/imunify360/integration.conf" logger = logging.getLogger(__name__) def get_hosting_panel(root_module: str): # pragma no cover # note: keep the panel test order in sync with the deploy script, # to avoid detecting conflicting panels in agent vs. the deploy script if is_generic_panel_installed(): # Checking this panel first is convenient for development, since # it allows you to turn any panel in the Generic Panel simply by # creating `/etc/sysconfig/imunify360/integration.conf`. module = import_module(f"{root_module}.subsys.panels.generic.panel") return module.GenericPanel() elif is_plesk_installed(): module = import_module(f"{root_module}.subsys.panels.plesk.panel") return module.Plesk() elif is_cpanel_installed(): module = import_module(f"{root_module}.subsys.panels.cpanel.panel") return module.cPanel() elif is_directadmin_installed(): module = import_module( f"{root_module}.subsys.panels.directadmin.panel" ) return module.DirectAdmin() elif root_module == "defence360agent": module = import_module(f"{root_module}.subsys.panels.no_cp.panel") return module.NoCP() module = import_module(f"{root_module}.subsys.panels.no_cp.panel") return module.NoControlPanel() def is_cpanel_installed(): return _is_panel_installed(CPANEL_FILE) def is_directadmin_installed(): return _is_panel_installed(DA_FILE) def is_generic_panel_installed(): return _is_panel_installed(GP_FILE) def is_plesk_installed(): return _is_panel_installed(PLESK_FILE) def _is_panel_installed(panel_file): return Path(panel_file).is_file() defence360agent/application/settings.py0000644000000000000000000000336300000000000015270 0ustar """Set settings of application object""" import os from pathlib import Path from defence360agent import files from defence360agent.application import tags from defence360agent.contracts import eula from defence360agent.internals.global_scope import g from defence360agent.model import simplification from defence360agent.rpc_tools.validate import ( SchemaValidator, validate_middleware, ) from defence360agent.simple_rpc.schema import init_validator from defence360agent.wordpress.plugin import update_wp_rules_on_sites from . import app async def update_eula_data(index, is_updated: bool): if is_updated: await eula.update() def configure( init_validator=init_validator, validator_cls=SchemaValidator, validate_middleware_wrap=validate_middleware, schema_paths=None, models_modules=None, set_sentry_tags=tags.fill, migration_dirs=None, migrations_attached_dbs=None, resident=False, ): if os.environ.get("DEBUG") == "true": g.DEBUG = True app.SCHEMA_PATHS = schema_paths app.VALIDATOR, app.MIDDLEWARE, app.MIDDLEWARE_EXCLUDE = init_validator( validator_cls, validate_middleware_wrap, schema_paths ) app.MODULES_WITH_MODELS += [simplification] if models_modules: app.MODULES_WITH_MODELS += models_modules av_path = Path(__file__).resolve().parent.parent app.MIGRATIONS_DIRS += [av_path / "migrations"] if migration_dirs: app.MIGRATIONS_DIRS += migration_dirs if migrations_attached_dbs: app.MIGRATIONS_ATTACHED_DBS += migrations_attached_dbs set_sentry_tags() files.configure() if not resident: files.Index.add_hook(files.EULA, update_eula_data) files.Index.add_hook(files.WP_RULES, update_wp_rules_on_sites) defence360agent/application/tags.py0000644000000000000000000000611400000000000014363 0ustar import json import logging import subprocess from pathlib import Path from defence360agent.contracts import sentry from defence360agent.contracts.config import Core as Config from defence360agent.contracts.license import LicenseCLN from defence360agent.internals.iaid import IndependentAgentIDAPI from defence360agent.subsys.panels import hosting_panel from defence360agent.utils import ( stub_unexpected_error, is_root_user, ) from defence360agent.utils.ipecho import IPEchoAPI logger = logging.getLogger(__name__) SENTRY_TAGS_CACHE_PATH = Path("/var/imunify360/.sentry_tags") def dump_sentry_tags(): SENTRY_TAGS_CACHE_PATH.write_text(json.dumps(sentry._TAGS)) def cached_fill(): if SENTRY_TAGS_CACHE_PATH.exists(): try: sentry._TAGS = json.loads(SENTRY_TAGS_CACHE_PATH.read_text()) return except (json.JSONDecodeError, FileNotFoundError) as e: logger.warning( "Sentry cache file %s is malformed: %s", SENTRY_TAGS_CACHE_PATH, e, ) except PermissionError: pass fill(dump=False) def _set_additional_tags(): PRIMARY_IDS_STRATEGY = "PRIMARY_IDS" CSF_COOP_STRATEGY = "CSF_COOP" def _is_firewalld_running() -> bool: try: subprocess.check_output( ["firewall-cmd", "--state"], timeout=5, stderr=subprocess.DEVNULL, ) return True except ( IOError, subprocess.CalledProcessError, subprocess.TimeoutExpired, ): return False def _is_csf_running() -> bool: try: out = subprocess.check_output( ["/usr/sbin/csf", "--status"], stderr=subprocess.DEVNULL ) except (FileNotFoundError, subprocess.CalledProcessError): return False return (b"have been disabled" not in out) and ( b"You have an unresolved error when starting csf:" not in out ) @stub_unexpected_error def _get_current_firewall(): if _is_csf_running(): return "csf" if _is_firewalld_running(): return "firewalld" return "iptables" fw = _get_current_firewall() sentry.set_firewall_type(fw) if fw == "csf": sentry.set_strategy(CSF_COOP_STRATEGY) else: sentry.set_strategy(PRIMARY_IDS_STRATEGY) def fill(dump=True) -> None: @stub_unexpected_error def _get_hosting_panel(): return hosting_panel.HostingPanel().NAME sentry.set_av_version(Config.AV_VERSION) sentry.set_core_version(Config.CORE_VERSION) sentry.set_version(Config.VERSION) sentry.set_product_name(LicenseCLN.get_product_name()) if not is_root_user(): return sentry.set_server_id(LicenseCLN.get_server_id()) sentry.set_iaid(IndependentAgentIDAPI.get_iaid()) sentry.set_ip(stub_unexpected_error(IPEchoAPI.server_ip)()) sentry.set_hosting_panel(_get_hosting_panel()) sentry.set_test_env() _set_additional_tags() if dump: dump_sentry_tags() defence360agent/contracts/0000755000000000000000000000000000000000000012546 5ustar defence360agent/contracts/__init__.py0000644000000000000000000000000000000000000014645 0ustar defence360agent/contracts/__pycache__/0000755000000000000000000000000000000000000014756 5ustar defence360agent/contracts/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030400000000000022153 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/__init__.pyrsrdefence360agent/contracts/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021214 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/__init__.pyrsrdefence360agent/contracts/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000020714400000000000021674 0ustar `j| UdZddlZddlZddlZddlmZddlmZmZddl m Z ddl m Z ddl m Z mZddlmZdd lmZdd lmZmZmZmZmZmZmZmZmZmZmZdd lm Z dd l!m"Z"m#Z#m$Z$m%Z%m&Z&dd l'm(Z)ddl*m+Z,ddl-m.Z.m/Z/m0Z0e0j1dddZ2ej3e4Z5e0j6d Z7dZ8dZ9e0j1dde2Z:dZ;eej<1ddZ=d\Z>Z?d\Z@ZAZBZCdZDdZEdZFdZGdZHdZIdZJdZKd ZLd!ZMd"ZNd"ZOd#ZPd$ZQd%ZRd&ZSd'\ZTZUZVd(\ZWZXZYd)ZZd*\Z[Z\d+Z]ej<fd,e^d-e_d.ed/e_fd0Z`ej<fd,e^d-ead.ed/eafd1Zbd2Zcd3Zdd4Ze dd5e^dzd/eee^dzffd6Zfd7ZgGd8d9ZhGd:d;ZidZjejkd"?d@Zlejkd"?dAZmdBdCdDddr$r?es U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config.pyint_from_envvarrHqsuD3s8}}  DDD/66s;;<r$r? TRUE_VALS FALSE_VALSvals rGbool_from_envvarrXzs/I/J  #hiikk )  4 *  5 % , ,S)j2H I I    sA A#"A#ctjtjt|SN)ospathjoindirname__file__relpaths rG _self_rel2absrbs& 7<<117 ; ;;ctjtjt t |SrZ)r[r\r]r^rb AGENT_CONFr`s rG conf_rel2absrfs. 7<< j(A(ABBG L LLrcc t|d5}|cdddS#1swxYwYdS#t$rYdSwxYw)z1Returns content for existing file, otherwise NonerN)openreadstripOSError)r\rPs rG _slurp_filerms $__ $6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ tts3A&A AA  A A  A A A usernamecVt|}||}|||}|||fStd||t}|||t jfS)z Choose action for config option by checking EndUser's Imunify360 config and Admin config. Admins config applies only if EndUser didn't set the default action rnNz"Cannot read %s:%s from user config) ConfigFileconfig_to_dictgetloggerdebugUserTypeROOT)sectionoptionrn user_config user_section user_value root_configs rGchoose_value_from_configr~sh///>>@@K??7++L!%%f--  !x' ' LL5wGGG,,--//K w  ' 66rccJtjtS)zG Just checks if this is server with MyImunify Freemium license )r[r\existsFREEMIUM_FEATURE_FLAGrcrGis_mi_freemium_licensers 7>>/ 0 00rcceZdZddZdZdS) FromConfigNc>||_||_||_d|_dSrZ)rxry _config_cls_config_instance)selfrxry config_clss rG__init__zFromConfig.__init__s&  % $rcc|j4|jt|_n||_|j|j}|j ||jS|SrZ)rrrqrrrxry)rinstanceowner section_values rG__get__zFromConfig.__get__sm  ('(2 %%(,(8(8(:(:%-<<>>t|L ; " - -rcNN)__name__ __module__ __qualname__rrrrcrGrrs7%%%%     rcrc8eZdZedZedddZdZdS) FromFlagFile/var/imunify360.coercer$c0||_||_||_dSrZ)r#rr$)rr#rr$s rGrzFromFlagFile.__init__s   rcc|j|jz }|r.||p|jSdSrZ)LOCATIONr#rr read_textr$)rrrr\s rGrzFromFlagFile.__get__sP}ty( ;;== A;;t~~//?4<@@ @ A ArcN)rrrr rboolrrrrcrGrrsVt%&&H'+S AAAAArcrTrootc|rdnd}i}tjtgD]0}t||d}|}t ||d1|S)Nget_root_configget_non_root_configciSrZrrrcrGz1_get_combined_validation_schema..srcF)allow_overwrite)r! iter_modulesCONFIG_VALIDATORS_DIR_PATHgetattrr )r func_namecombined_schemar" get_schemaschemas rG_get_combined_validation_schemarst%)D!!/DIO')C(DEEIIVY ;; &%HHHHH rc)maxsizectSrZrrrcrGconfig_schema_rootrs * , ,,rcc"tdS)NFrrrrcrGconfig_schema_non_rootrs * 6 6 66rcCUSTOM_BILLINGdictstring)typer$nullableboolean)rr$) upgrade_urlupgrade_url_360billing_notifications ip_license)rrr$ceZdZdS)ConfigValidationErrorN)rrrrrcrGrrsDrcrceZdZdZesdezndZeZeZ e Z e j ddZdZdZdZd Zd Zd Zd Zd Zd Ze jedZdZe jeeZe jeeZdZdZ e jed Z!dZ"dZ#dZ$dZ%dZ&esdndZ'dZ(e)dZ*dZ+dS)Core imunify360z%s agentzimunify antivirusIMUNIFY360_API_URLzhttps://api.imunify360.com z.el8z/var/imunify360/tmpzimunify360-merged.configz&imunify360-merged-nonprivileged.configzimunify360.configz/etc/imunify360rz/etc/sysconfig/imunify360iizimunify360.config.dz.imunify360.backup_configz hooks.yamlzcustom_billing.configz/var/imunify360/hookszimunify360-agentzimunify-antiviruszunified-access-logger.confz#/etc/sysconfig/imunify360/.go_agent<N),rrrPRODUCTANTIVIRUS_MODENAME av_version AV_VERSION core_version CORE_VERSION_versionVERSIONr[environrs API_BASE_URLDEFAULT_SOCKET_TIMEOUTDIST FILE_UMASKTMPDIRMERGED_CONFIG_FILE_NAME%MERGED_NONPRIVILEGED_CONFIG_FILE_NAMEUSER_CONFIG_FILE_NAMELOCAL_CONFIG_FILE_NAME CONFIG_DIRr\r] USER_CONFDIRGLOBAL_CONFDIRMERGED_CONFIG_FILE_PATH%MERGED_NONPRIVILEGED_CONFIG_FILE_PATHMERGED_CONFIG_FILE_PERMISSION+MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSIONLOCAL_CONFIG_FILE_PATH CONFIG_D_NAMEBACKUP_CONFIGFILENAMEHOOKS_CONFIGFILENAMECUSTOM_BILLING_CONFIGFILENAMEINBOX_HOOKS_DIRSVC_NAME$UNIFIED_ACCESS_LOGGER_CONFIGFILENAMEr GO_FLAG_FILE%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECSrrcrGrr sVG'5 N:  ;NDJLG:>>:L  DJ "F80*00"J7<< M::L0N gll/-/GLL=--)%*!27/W\\.:MNN)M7'$;!-O#1I6I ,H(4=>>L,.)))rcrc eZdZe ddededefdZe ddededed ed edd f d Zedd Z e dded edefdZ ede e defdZ dede edefdZdede ededd fdZd S)IConfigTF normalize force_readr@ctrZNotImplementedErrorrrrs rGrrzIConfig.config_to_dict7 "!rcdatavalidate overwritewithout_defaultsNctrZrrrrrrrs rGdict_to_configzIConfig.dict_to_config=s "!rcctrZrrs rGrzIConfig.validateH!!rcconfigctrZrrrrs rGrzIConfig.normalizeLrrc timestampctrZrrrs rGmodified_sincezIConfig.modified_sinceRrrcrxryc|r;||i|SdSrZ)rrrs)rrxrys rGrsz IConfig.getVs@  F&&((,,Wb99==fEE Etrcvaluec@|r||||iidSdSrZ)r)rrxryrs rGsetz IConfig.set[s7  <   65/ : ; ; ; ; ; < """26" """^" !& """" "  "  " """^""""^"8="""15" """^" ""D"""^"3 # <3< z;Normalizer._get_schema_without_defaults..xs]   U777 %&&11%888777rcitems)r)r"s` rGrz'Normalizer._get_schema_without_defaultsvs8    $kkmm     rcrci}|D]W\}}t|tr6|D] \}}||||i|<!P|r|||<X|SrZ)r,r&r setdefault)r new_configrxoptionsryrs rG remove_nullzNormalizer.remove_nulls:< &  . . GW'4(( .%,]]__KKMFE(EJ --gr::6BK .'. 7#rcct|}||}|jrt|j|td||S)NzCerberus returned None for )ConfigValidator normalizederrorsr)rr validatorr4s rG_normalize_with_schemaz!Normalizer._normalize_with_schemasa#F++ %.%9%9&%A%A   :' (899 9  '(Nf(N(NOO Orcrc|r|jn|j}|r+||}|||S||jkr|jS|||}||_||_|jSrZ)r rr1r7rr)rrrrr4s rGrzNormalizer.normalizes-= OD ) )4<   ?%%f--F..vv>> > T\ ! !* *00@@  ",&&rcN) rrrr classmethodrrr staticmethodr1r7rrrrcrGrrks     T   [  G    \ w4\ ' '4 'D ' ' ' ' ' 'rcrc eZdZdZddddddddededeeegeffded e d e f fd Z d Z ddede fdZdde de fdZ d dede de de de ddf dZdZdZdZdeede fdZxZS)!ConfigNT)r\ config_readerr! disclaimercached permissionsr\r>r!r?r@rAct|s|sJ|rtnt}|p|||p|j||_|jj|_|pt|_t|j|_ dS)N)r?rA) superrrr DISCLAIMER_config_readerr\rr!r _normalizer) rr\r>r!r?r@rAconfig_reader_cls __class__s rGrzConfig.__init__s $}$$$28J..l+ /@/@ !4T_#0 0 0  ', !2!H6H%d&<==rccZd|jj|j|jS)NzW<{classname}(config_reader={config_reader!r}, validation_schema={validation_schema!r})>) classnamer>r!)rErHrrEr!rs rG__repr__zConfig.__repr__s6  &n1-"4    rcFrr@c8|j||SrZ)rFrrs rGrzConfig.normalizes))&2BCCCrcrc|j|}|r||}t|S)zr Converts config file to dict :return dict: dictionary (key (section) / value (options)) )r)rEread_config_filerr)rrrrs rGrrzConfig.config_to_dictsD $555LL  ,^^F++Frcrrrrrcr|r|||||dS|||||dS)a Converts dict to config file New options will be mixed in with old ones unless overwrite is specified :param dict data: dictionary (key (section) / value (options)) :param bool validate: indicates if we need validation :param bool normalize: normalize config :param overwrite: overwrite existing conf :param without_defaults: do not fill defaults :return: None )rrrrN)_dict_to_config_overwrite_dict_to_configrs rGrzConfig.dict_to_configst(    * *!#!1 +       !#!1 !     rcc|r t||j|r|||}|j|dSNr)rrr!rrEwrite_config_file)rrrrrs rGrPz Config._dict_to_config_overwritesb  D  % %dD,B C C C  K>>$9I>JJD --d33333rcc t|j}t||rW|r t||j|r|||}|j|dSdSrS) rrErNr rrr!rrU)rrrrrrs rGrQzConfig._dict_to_config s$->>@@AA FD ) ) : J ))&$2HIII -=(   1 1& 9 9 9 9 9 : :rcc |jd}nB#t$r5}d}td||t ||i|d}~wwxYw t ||jdS#t$r5}d}td||t ||i|d}~wwxYw)z/ :raises ConfigsValidatorError F) ignore_errorszError during config validationz%s: %sNz+Imunify360 config does not match the scheme) rErNrrterrorConfigsValidatorErrorrrr!r)r config_dictrFmessages rGrzConfig.validates @->>#?KK @ @ @6G LL7A . . .'w88a ? @  @  % %k43I J J J J J$ @ @ @CG LL7A . . .'w88a ? @s, A0AA! B C 0B==Crc6|j|SrZ)rErrs rGrzConfig.modified_since("11)<<>> > $ > !(2w;*?!?@ >>>>>>>>>.    DDDDDDDD     $     !& !!!! !  !  ! !!!!F444 : : :@@@(==D========rcr<) metaclassceZdZfdZxZS) UserConfigc||_tjtj|tj}t|t||tdS)N)r\r>r!) rnr[r\r]rrrrCrrr)rrnr\rHs rGrzUserConfig.__init__-sl  w||  x)C   *4::4      rcrrrrr_r`s@rGrcrc,s8          rcrcc eZdZdddddededeffdZ dd ed ed efd Z dd eded ededed df dZ ddZ ddeded efdZ de e d efdZxZS) SystemConfigN) local_config merged_confignonpriv_merged_configrhrirjct|p t|_|p t |_|p t |_dSrZ)rCr LocalConfig _local_config MergedConfig_merged_configMergedNonPrivilegedConfig_nonpriv_merged_config)rrhrirjrHs rGrzSystemConfig.__init__:sZ ):[]]+=|~~ ! @%>%@%@ ###rcTFrrr@c:|j||S)Nrr)rorrrs rGrrzSystemConfig.config_to_dictHs)"11J2   rcrrrrcD|j|||||dSN)rrrr)rmrrs rGrzSystemConfig.dict_to_configOs= )) - *     rccj|j|jdSrZ)rorrqrs rGrzSystemConfig.validate_s2 $$&&& #,,.....rcrc:|j||S)N)rr)rorrs rGrzSystemConfig.normalizecs*",,,<-   rcrc6|j|SrZ)rorrs rGrzSystemConfig.modified_sincejr^rcr TTFTr r)rrrr<rrrrrrrrrrrrr_r`s@rGrgrg9sw $ $(,        &       :?   26     !%                //// 9>   15     ==D========rcrgr\c|r%t|tst|S|rt|St S)Nrpr\)r&rBrcr<rg)rnr\s rGconfig_file_factoryr|nsQ 8S118,,,, 4    ~~rcctt}|jD]}||rdSdS)NTF)Mergerget_layer_nameslayersr)rmergerlayers rGany_layer_modified_sincerzsS F**,, - -F    * * 44  5rc)r\r?rA)r>c eZdZdZejddddddedeee geffde ffdZ dd ed e d e d e de ddf fd Z xZS)rlzt Config (/etc/sysconfig/imunify360/imunify360.config) should contain options changed by a customer only NFr\r>r!r?r@r>r!r?cTt|||||dS)NrrCr)rr\r>r!r?r@rHs rGrzLocalConfig.__init__s? '/!      rcTrrrrrr@cPt|||||Sru)rCr)rrrrrrrHs rGrzLocalConfig.dict_to_configs5ww%% - &   rcry)rrrrrrrrrr rrrrr_r`s@rGrlrls (&*CG   $  !(2w;*?!?@         (!%                      rcrlceZdZejejejZ dZ d dZ e dZ d dZe defdZdS) BaseMergerz90-local.configFc<|_fd|D_dS)Ncvg|]5}ttjj|6S)r{)r<r[r\r]DIR)r'r#rs rG z'BaseMerger.__init__..sC   :>F TXt44 5 5 5   rc)_include_defaultsr)rnamesinclude_defaultss` rGrzBaseMerger.__init__s9!1    BG    rcctj|jr&t tj|jngSrZ)r[r\isdirrsortedlistdirr)s rGrzBaseMerger.get_layer_namess6.0gmmCG.D.DLvbj))***"Lrccg}|jr>ttid}|||fd|jDz }||S)NFrTc>g|]}|dS)Frs)rr)r'rrs rGrz.BaseMerger.configs_to_dict..s<     5Z H H   rc)rrrrappendr_build_effective_config)rrlayer_dict_listdefaultss ` rGconfigs_to_dictzBaseMerger.configs_to_dicts  ! -!"455??U@H  " "8 , , ,        ++O<< ? ?C)    MM[M = = = = d   [   rcrc$eZdZdeffd ZxZS) MutableMergerrct||j}|d|}t|ddSNTr)rrrCrrridxrHs rGrzMutableMerger.__init__sE%!788dsd  66666rcrrrrrr_r`s@rGrrsD7h7777777777rcrc$eZdZdeffd ZxZS)ImmutableMergerrct||j}||d}t|ddSNFr)rrrCrrs rGrzImmutableMerger.__init__sE5$"899cdd  77777rcrr`s@rGrrsD8h8888888888rcrc$eZdZdeffd ZxZS) NonBaseMergerrcNt|ddSrrrrrHs rGrzNonBaseMerger.__init__s& 77777rcrr`s@rGrrsD8h8888888888rcrcneZdZddgddgdgdgdZfdZed Zed ed efd Z xZ S) r~Nuser_override_proactive_defensenum_dayslimitenableenable_scan_modsec)PROACTIVE_DEFENCE PERMISSIONSINCIDENT_LOGGINGERROR_REPORTINGMALWARE_SCANNINGcNt|ddSrrrs rGrzMerger.__init__s& 66666rcc||}|}|jD]I}tj|js#t d|jdSJ t |tt}| |d}| |\}}t|dt!|dddS#t"t$f$r&}t d|Yd}~dSd}~wwxYw)NzGAborting merged config update: Config layer %s disappeared during mergeFrT)r)rrzConfig file is invalid! %s)rrrr[r\lexistsrtwarningrrrrr_split_settingsrnrrprZr)r)rr[r normalizer priv_dict nonpriv_dictrFs rGupdate_merged_configzMerger.update_merged_configsS((**++,,.. ]  E7??5:.. ?J      % %k 2 2 2 $$677J$..e/K'*&9&9+&F&F #I| NN ) ))e ) D D D % ' ' 6 6u 7     &'<= < < < NN7 ; ; ; ; ; ; ; ; ; s[)) (* # : @ @ B B   GWk))(0W1E(F(F W%%%FW!555",6646L18CG8L"9 W-f5 ,&&rc) rrrrrr9rrtuplerr_r`s@rGr~r~s! -     ! &77777))[)V'$'5'''['''''rcr~c,eZdZeddfd ZdZxZS)r3T allow_unknownpurge_readonlyc@tj|||d|dS)z Initialises ConfigValidator(Validator) for more details on Validator params please check https://docs.python-cerberus.org/en/stable/validation-rules.html rNr)rrrargskwargsrHs rGrzConfigValidator.__init__[sB  ')        rcch|jdiddrdS|S)Nr&rFT) root_documentrs)rrs rG(_normalize_coerce_user_override_pd_rulesz8ConfigValidator._normalize_coerce_user_override_pd_rulesns7   ! !, 3 3 7 7% H H 4 rc)rrrrrrr_r`s@rGr3r3ZsY%        &rcr3c"eZdZdejzZdS) Packagingz/opt/imunify360/venv/share/%sN)rrrrrDATADIRrrcrGrrts- /var/imunify360/files/geo/v1/GeoLite2-Country-Locations-en.csvc,d|S)Nz2/var/imunify360/files/geo/v1/CountrySubnets-{}.txt)rE) country_codes rGcountry_subnets_filez CountryInfo.country_subnets_filesCJJ    rcN)rrrDB LOCATIONS_DBr:rrrcrGrrs? =B I  \   rcrcjeZdZejdedejzZe ddZ dS)SentryIMUNITY360_SENTRY_DSNz %s/sentryrrN) rrrr[getenvrmrrDSNrENABLErrcrGrrsL ")-L!M!M  CZ)8 4 4FFFrcrceZdZdZdZdZdZdZeddZ eddZ eddZ edd Z edd Z edd Zedd Zedd ZdZdZdZeddZeddZeddZeddZeddZeddZeddZeddZeddZeddZedZeddZ d S)!Malwarei,rrrrmax_targets_per_scan_type max_path_lenenable_scan_inotifyenable_scan_pure_ftpdsends_file_for_analysiscloud_assisted_scan rapid_scancrontabsz$/var/imunify360/aibolit/scans.pickleiz/var/imunify360/cleanup_storageMALWARE_CLEANUPtrim_file_instead_of_removalrxrykeep_original_files_daysscan_modified_filesmax_signature_size_to_scanmax_cloudscan_size_to_scanmax_mrs_upload_file,rapid_scan_rescan_unchanging_files_frequency hyperscanMALWARE_DATABASE_SCANrenable_scan_cpaneldisable_cloudav db_timeoutN)!rrrSCAN_CHECK_PERIODCONSECUTIVE_ERROR_LIMITINOTIFY_SCAN_PERIODCONFIG_CHECK_PERIODCONFLICTS_CHECK_PERIODrMAX_TARGETS_PER_SCAN_TYPE MAX_PATH_LENINOTIFY_ENABLED PURE_SCAN SEND_FILESCLOUD_ASSISTED_SCAN RAPID_SCANCRONTABS_SCAN_ENABLED SCANS_PATHFILE_PREVIEW_BYTES_NUMCLEANUP_STORAGE CLEANUP_TRIM CLEANUP_KEEPSCAN_MODIFIED_FILESMAX_SIGNATURE_SIZE_TO_SCANMAX_CLOUDSCAN_SIZE_TO_SCANMAX_MRS_UPLOAD_FILE,RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY HYPERSCANDATABASE_SCAN_ENABLEDCPANEL_SCAN_ENABLEDrCLEANUP_DISABLE_CLOUDAVMDS_DB_TIMEOUTrrcrGr r s   * 7!!:0.AAL j!35JKKO -/FGGI.0IJJJ$*%79NOO. ==J&J'9:FF7J'7O:!-L:!)L%*"$ ",8""",8""%*%79NOO3=:J440 -{;;I&J'>II$*%79MNN*l+<==Z 7FFNNNrcr cpeZdZdZedZedZededZedZ dS) MalwareTunezc Experimental and testing-only purpose settings we don't want to expose to customers. use_jsonno_check_known_hashesrapid_scan_basedir_overridez/homerno_auto_upgradeN) rrrrrUSE_JSON_REPORTNO_CHECK_KNOWN_HASHESr RAPID_SCAN_BASEDIR_OVERRIDENO_AUTO_UPGRADErrcrGr=r=sj #l:..O(L)@AA".,%dG####l#455OOOrcr=ceZdZeZeZeZeZdS)MalwareScanScheduleIntervalN)rrrNONEDAYWEEKMONTHrrcrGrGrGs" D C D EEErcrGceZdZdZdZdZeddZeddZeddZ edd Z d S) MalwareScanSchedulez8/usr/bin/imunify360-agent malware user scan --backgroundz!/etc/cron.d/imunify_scan_schedulezS# DO NOT EDIT. AUTOMATICALLY GENERATED. 0 {0} {1} * {2} root {cmd} >/dev/null 2>&1 MALWARE_SCAN_SCHEDULEintervalrhour day_of_week day_of_monthN) rrrCMD CRON_PATH CRON_STRINGrINTERVALHOUR DAY_OF_WEEK DAY_OF_MONTHrrcrGrMrMs DC3IK z'H :'   D*'K:'LLLrcrMceZdZeddZeddZeddZeddZeddZeddZ edd Z d S) MalwareScanIntensityMALWARE_SCAN_INTENSITYcpurioram user_scan_cpu user_scan_io user_scan_ram resident_ramN) rrrrCPUIORAMUSER_CPUUSER_IOUSER_RAM RESIDENT_RAMrrcrGr[r[(s *(   C (   B *(   Cz(Hj(Gz(H:(LLLrcr[c\eZdZeddZeddZeddZdS)FileBasedResourceLimitsRESOURCE_MANAGEMENT cpu_limitrio_limit ram_limitN)rrrrrdrerfrrcrGrlrlGsf *%   C %   B *%   CCCrcrlc(eZdZeddZdS) KernelCare KERNELCAREedfrN)rrrrEDFrrcrGrrrrVs* *   CCCrcrrctj}|Rtjdtjdtjdtjdi}|tj dS|S)Nr2r.rr) r r6rGrHrKrJrIrsrMrV)rfreqs rGget_rapid_rescan_frequencyrx]sU  @E } ' ,a ' -q ' ,a ' +R   xx+4a888 LrcceZdZdZejedZejedZejeddZ ejeddZ ejeddZ ejeddZ ejedd Z d S) MalwareSignaturesz/var/imunify360/files/sigs/v1/rfxni360aibolitzai-bolit-hoster-full.dbrzmds-ai-bolit-hoster.dbz procu2.dbz mds-procu2.dbN)rrr_dirr[r\r]RFXNr|AI_BOLIT_HOSTERAI_BOLIT_HYPERSCANMDS_AI_BOLIT_HOSTERPROCU_DB MDS_PROCU_DBrrcrGrzrzjs +D 7<<f % %D 7<<f % %Dgll44MNNOdI{CC',, i1w||D)[99H7<<iAALLLrcrzcJeZdZeddZeddZdZdZdS)LoggerLOGGERmax_log_file_sizer backup_countiiN)rrrrMAX_LOG_FILE_SIZE BACKUP_COUNT LOG_DIR_PERM LOG_FILE_PERMrrcrGrrxsU" ":L LMMMrcrceZdZdZdZdS)rvrnon_rootN)rrrrwNON_ROOTrrcrGrvrvs DHHHrcrv caller_type)r$ceZdZdZdZdS)UIRoleclientadminN)rrrCLIENTADMINrrcrGrrs F EEErcrceZdZdZdZdS)NoCPz/etc/imunify360/scripts/domainsr2N)rrr CLIENT_SCRIPTLATEST_VERSIONrrcrGrrs5MNNNrcrceZdZfdZxZS)CustomBillingConfigctjdtj}t |tdS)Nrr\r!)r[r\r]rrrCrCONFIG_SCHEMA_CUSTOM_BILLING)rr\rHs rGrzCustomBillingConfig.__init__sSw|| ')K   )E      rcrer`s@rGrrs8         rcrc~eZdZeddeZeddeZeddeZeddeZdS) CustomBillingrrrxryrrrrN) rrrrr UPGRADE_URLUPGRADE_URL_360 NOTIFICATIONS IP_LICENSErrcrGrrs* &K !j  &O J &&M  &JJJrcrceZdZeddZeddZeddZeddZeddZeddZ edd Z d S) PermissionsConfigruser_ignore_listrallow_malware_scanuser_override_malware_actionsr*allow_local_malware_ignore_list_managementuse_plesk_service_planallow_wp_waf_rules_managementN) rrrrUSER_IGNORE_LISTALLOW_MALWARE_SCANUSER_OVERRIDE_MALWARE_ACTIONSUSER_OVERRIDE_PROACTIVE_DEFENSE*ALLOW_LOCAL_MALWARE_IGNORE_LIST_MANAGEMENTUSE_PLESK_SERVICE_PLANALLOW_WP_WAF_RULES_MANAGEMENTrrcrGrrs!z!$#%/J&E%%%!'1j0'''#2<;222.(Z'%/J.%%%!!!rcrcBeZdZeddZeddZdS)MyImunifyConfigr&rrpurchase_page_urlN)rrrrENABLEDPURCHASE_PAGE_URLrrcrGrrsKjG# "rcrcBeZdZeddZeddZdS)ControlPanelConfig CONTROL_PANELsmart_advice_allowedradvice_email_notificationN)rrrrSMART_ADVICE_ALLOWEDADVICE_EMAIL_NOTIFICATIONrrcrGrrsL%:%!+ *!!!rcrcgd}tjtjtjtjtjtjd||diddd<fdfd|D}t ||jS) N) BACKUP_RESTORErrrrrr&rrN WORDPRESS))rNrO)rNrP)rNrQ)rNrR)rdefault_action)rmoder waf_enabledT)rrc@t|i}t |i}i}|D]?\}}||}|||fdr|||<:|||<@|S)NT)rrsr,) rx admin_options user_optionsresulting_dictry admin_valuer| admin_dict overridable user_dicts rGnormalize_sectionz0effective_user_config..normalize_section!s !.2s407""7++rc)rrrrrrsfm_config_cleanuprn) admin_configrzallowed_sectionseffective_configrrrrs @@@@rGeffective_user_configrs      :  :  :  :  :  <1K4,,..J**,,I1;R11 c-,-";K -{/C D DDrcc:eZdZdxZ\ZZZZZeeeefZ e re neZ dS) HookEvents)agentlicensezmalware-scanningzmalware-cleanupzmalware-detectedN) rrr IM360_EVENTSAGENTLICENSErrMALWARE_DETECTED IMAV_EVENTSrEVENTSrrcrGrr9sS  L   K + <[[ FFFrcrc0eZdZdeeeffdZdZdS)rZconfigs_to_errorsc||_dSrZ)r)rrs rGrzConfigsValidatorError.__init__Qs!2rccg}|jD]\}}||d| d|S)Nz:  )rr,rr])rr5rrYs rGrKzConfigsValidatorError.__repr__Ts^!399;; 2 2MFE MMV0000 1 1 1 1yy   rcN)rrrrr<rrrKrrcrGrZrZPsG3$vs{*;3333!!!!!rcrZceZdZdZedZedZeefdede ee fddfdZ e de e e fde fd ZdS) rz@A class that has methods to validate configs bypassing the cachecFtdS)zN Validate merged config :raises ConfigsValidatorError N)rgrrs rGvalidate_system_configz'ConfigsValidator.validate_system_config^s !!!!!rcci}ttjD]H} |#t$r$}||jYd}~Ad}~wwxYw|rt |dS)zf Validate all config layers, collect all errors :raises ConfigsValidatorError N)r~rrrrZupdater)r)rrrFs rGvalidate_config_layersz'ConfigsValidator.validate_config_layersfs F224455< > >E >    ( > > >!(()<======== >  ;'(9:: : ; ;sA A4A//A4r[r!r@Nc||}t|}||st|jdS)z Validate config represented by a dict :param config_dict: config to validate :param validation_schema: schema to validate config against :raises ConfigValidationError N)rr3rrr5)r)r[r!rvs rGrzConfigsValidator.validatevsS**+<== F # #zz+&& 2'11 1 2 2rcc8t|r |S|SrZ)callable)r!s rGrz&ConfigsValidator.get_validation_schemas* % & & '$$&& &  rc)rrrrr9rrrrrr rr:rrrrcrGrr[sJJ""[" ; ;[ ;4F222!x02  222[2"! (!23! !!!\!!!rcrc(eZdZeddZdS) AdminContactsADMIN_CONTACTSenable_icontact_notificationsrN)rrrrENABLE_ICONTACT_NOTIFICATIONSrrcrGrrs-$.J .%%%!!!rcrc eZdZdZdZdZdZdS)IContactMessageType MalwareFoundScanNotScheduledGenericc|jSrZ)rrs rG__str__zIContactMessageType.__str__s zrcN)rrr MALWARE_FOUNDSCAN_NOT_SCHEDULEDGENERICrrrcrGrrs3"M+Grcr BACKUP_SYSTEMF)rr$rallowed)enabled backup_systemcfeZdZdZejdeje dfd Z xZ S) BackupConfigaW# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # DO NOT EDIT. AUTOMATICALLY GENERATED. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # Direct modifications to this file WILL be lost upon subsequent # regeneration of this configuration file. # # To have your modifications retained, you should use CLI command # imunify360-agent backup-systems # or activate/deactivate appropriate feature in UI. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # rrcNt||dS)Nrr)rr\r!rHs rGrzBackupConfig.__init__s) d6GHHHHHrc) rrrrDr[r\r]rrCONFIG_SCHEMA_BACKUP_SYSTEMrr_r`s@rGrrsvJ(W\\ ')C  6 IIIIIIIIIIIrcrceZdZeddeZeddeZeddZeddZe d Z d S) BackupRestorerrrrrcl_backup_allowedrcl_on_premise_backup_allowedc*t|jSrZ)_get_backend_system_BACKUP_SYSTEMrs rGrzBackupRestore.backup_systems"3#5666rcN) rrrrrrrCL_BACKUP_ALLOWEDCL_ON_PREMISE_BACKUP_ALLOWEDr9rrrcrGr r sj lG ZN #  "$.: -$$$ 77[777rcr ctddlm}|ttfvrt}n|dS||dS)zo Get backup module from its name :param name: backup system name :return: backup system module r)backup_backendsNT)async_)restore_infectedr CLOUDLINUXCLOUDLINUX_ON_PREMISEACRONISbackend)r#rs rGr r sQ 100000 1222 t  " "4 " 5 55rcceZdZdZdZdhZdS) AcronisBackupzacronis-installer.log)i ii)iZixN)rrrLOG_NAMEPORTSRANGErrcrGrrs!'H E NEEErcrcFtdd|\}}tjo|S)zs Checks is Agent should try restore malware file firts and returns user that set this action in config rtry_restore_from_backup_first)r~r r)rn try_restore_s rG should_try_autorestore_maliciousr"s/ .;XNK  0[0rccvtdd|\}}tjt|z }|S)Nrmax_days_in_backup)days)r~rnowr )rnmax_daysr!untils rG"choose_use_backups_start_from_dater)s?*.KHa LNNYH555 5E Lrcc0tdd|\}}|S)Nrgeneric_user_notificationsrp)r~)rn should_sendr!s rGshould_send_user_notificationsr-"s+-$NK rcceZdZeddZeddZeddZeddZeddZdS) Wordpressrsecurity_plugin_enabledr waf_defaultai_bot_protectionai_bot_protection_presetN) rrrrSECURITY_PLUGIN_ENABLED WAF_ENABLED WAF_DEFAULTAI_BOT_PROTECTIONAI_BOT_PROTECTION_PRESETrrcrGr/r/+sw(j.*[-88K*[-88K" ;0CDD)z/  rcr/ceZdZdZdZdZdZdS) HackerTraprzmalware_found_b64.listzmalware_standalone_b64.listz%/opt/imunify360/proactive/dangerlist/N)rrrrrSA_NAMEDIR_PDrrcrGr:r:7s" C #D+G 4FFFrcr:rZr)r functoolsloggingr[abcrbisectrr contextvarsrcopyrrr enumr pathlibr typingr r rrrrrrrrrcerberusr)defence360agent.contracts.config_providerrrrrr+defence360agent.feature_management.checkersrrdefence360agent._versionrrdefence360agent.utilsrr r!rsr getLoggerrrtrrrMY_IMUNIFY_KEYrrerrNOTIFYCLEANUPrHrIrJrKDEFAULT_INTENSITY_CPUDEFAULT_INTENSITY_IODEFAULT_INTENSITY_RAMDEFAULT_INTENSITY_RESIDENT_RAM%DEFAULT_RESOURCE_MANAGEMENT_CPU_LIMIT$DEFAULT_RESOURCE_MANAGEMENT_IO_LIMIT%DEFAULT_RESOURCE_MANAGEMENT_RAM_LIMITMODSEC_RULESET_FULLMODSEC_RULESET_MINIMAL_DOS_DETECTOR_DEFAULT_LIMIT_DOS_DETECTOR_MIN_LIMIT_DOS_DETECTOR_MIN_INTERVALPORT_BLOCKING_MODE_DENYPORT_BLOCKING_MODE_ALLOWDO_NOT_MODIFY_DISCLAMERDEFAULT_CONFIG_DISCLAMERCPANELPLESK DIRECTADMINrR1SOFT CLUSTERLOGICSSAMPLE_BACKENDrrGENERIC_SENSOR_SOCKET_PATHrrBrHrrXrbrfrmr~rrrr lru_cacherrr Exceptionrrrrrrr<rcrgr|rpartialrrrnrrrprlrrrrr~r3rrrrrrr r=rGrMr[rlrrrxrzrrvrwrrrrrrrrrrrrZrrqrrrrr r rr"r)r-r/r:rrcrGris ,,,,,,,,""""""((((((((                          A@@@@@GGGGGGGGGG X\    8 $ $$X_W---A 8< -    !TJNN">&7c4!%()%'($(+%"! " >{!E$I! !M<>:DDDsDD#DDDD-/J    ")      (<<<MMM-177"Tz7 3d ?7777&111( A A A A A A A A-1Q-- -Q77 7 !  !    /8D%I%I#,>>   ! ,     I   */*/*/*/*/*/*/*/Z'<'<'<'<'sm 4!y  ''  )*6 .I- ''  7*D% % % % % &% % % P((((((((V77777J77788888j88888888J888 \'\'\'\'\'Z\'\'\'~i4========4LLLLLLLL&         555555555G5G5G5G5G5G5G5Gp 6 6 6 6 6 6 6 64>            B B B B B B B B         *z-OOO Z_OOO      &   .<DEDEDEN========.!!!!!I!!!3!3!3!3!3!3!3!3!l! #t"  ! )!"     ,1<IIIII6III:77777777.666"1s1t1111ST        5555555555rcdefence360agent/contracts/__pycache__/config.cpython-311.pyc0000644000000000000000000020714400000000000020735 0ustar `j| UdZddlZddlZddlZddlmZddlmZmZddl m Z ddl m Z ddl m Z mZddlmZdd lmZdd lmZmZmZmZmZmZmZmZmZmZmZdd lm Z dd l!m"Z"m#Z#m$Z$m%Z%m&Z&dd l'm(Z)ddl*m+Z,ddl-m.Z.m/Z/m0Z0e0j1dddZ2ej3e4Z5e0j6d Z7dZ8dZ9e0j1dde2Z:dZ;eej<1ddZ=d\Z>Z?d\Z@ZAZBZCdZDdZEdZFdZGdZHdZIdZJdZKd ZLd!ZMd"ZNd"ZOd#ZPd$ZQd%ZRd&ZSd'\ZTZUZVd(\ZWZXZYd)ZZd*\Z[Z\d+Z]ej<fd,e^d-e_d.ed/e_fd0Z`ej<fd,e^d-ead.ed/eafd1Zbd2Zcd3Zdd4Ze dd5e^dzd/eee^dzffd6Zfd7ZgGd8d9ZhGd:d;ZidZjejkd"?d@Zlejkd"?dAZmdBdCdDddr$r?es U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config.pyint_from_envvarrHqsuD3s8}}  DDD/66s;;<r$r? TRUE_VALS FALSE_VALSvals rGbool_from_envvarrXzs/I/J  #hiikk )  4 *  5 % , ,S)j2H I I    sA A#"A#ctjtjt|SN)ospathjoindirname__file__relpaths rG _self_rel2absrbs& 7<<117 ; ;;ctjtjt t |SrZ)r[r\r]r^rb AGENT_CONFr`s rG conf_rel2absrfs. 7<< j(A(ABBG L LLrcc t|d5}|cdddS#1swxYwYdS#t$rYdSwxYw)z1Returns content for existing file, otherwise NonerN)openreadstripOSError)r\rPs rG _slurp_filerms $__ $6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ tts3A&A AA  A A  A A A usernamecVt|}||}|||}|||fStd||t}|||t jfS)z Choose action for config option by checking EndUser's Imunify360 config and Admin config. Admins config applies only if EndUser didn't set the default action rnNz"Cannot read %s:%s from user config) ConfigFileconfig_to_dictgetloggerdebugUserTypeROOT)sectionoptionrn user_config user_section user_value root_configs rGchoose_value_from_configr~sh///>>@@K??7++L!%%f--  !x' ' LL5wGGG,,--//K w  ' 66rccJtjtS)zG Just checks if this is server with MyImunify Freemium license )r[r\existsFREEMIUM_FEATURE_FLAGrcrGis_mi_freemium_licensers 7>>/ 0 00rcceZdZddZdZdS) FromConfigNc>||_||_||_d|_dSrZ)rxry _config_cls_config_instance)selfrxry config_clss rG__init__zFromConfig.__init__s&  % $rcc|j4|jt|_n||_|j|j}|j ||jS|SrZ)rrrqrrrxry)rinstanceowner section_values rG__get__zFromConfig.__get__sm  ('(2 %%(,(8(8(:(:%-<<>>t|L ; " - -rcNN)__name__ __module__ __qualname__rrrrcrGrrs7%%%%     rcrc8eZdZedZedddZdZdS) FromFlagFile/var/imunify360.coercer$c0||_||_||_dSrZ)r#rr$)rr#rr$s rGrzFromFlagFile.__init__s   rcc|j|jz }|r.||p|jSdSrZ)LOCATIONr#rr read_textr$)rrrr\s rGrzFromFlagFile.__get__sP}ty( ;;== A;;t~~//?4<@@ @ A ArcN)rrrr rboolrrrrcrGrrsVt%&&H'+S AAAAArcrTrootc|rdnd}i}tjtgD]0}t||d}|}t ||d1|S)Nget_root_configget_non_root_configciSrZrrrcrGz1_get_combined_validation_schema..srcF)allow_overwrite)r! iter_modulesCONFIG_VALIDATORS_DIR_PATHgetattrr )r func_namecombined_schemar" get_schemaschemas rG_get_combined_validation_schemarst%)D!!/DIO')C(DEEIIVY ;; &%HHHHH rc)maxsizectSrZrrrcrGconfig_schema_rootrs * , ,,rcc"tdS)NFrrrrcrGconfig_schema_non_rootrs * 6 6 66rcCUSTOM_BILLINGdictstring)typer$nullableboolean)rr$) upgrade_urlupgrade_url_360billing_notifications ip_license)rrr$ceZdZdS)ConfigValidationErrorN)rrrrrcrGrrsDrcrceZdZdZesdezndZeZeZ e Z e j ddZdZdZdZd Zd Zd Zd Zd Zd Ze jedZdZe jeeZe jeeZdZdZ e jed Z!dZ"dZ#dZ$dZ%dZ&esdndZ'dZ(e)dZ*dZ+dS)Core imunify360z%s agentzimunify antivirusIMUNIFY360_API_URLzhttps://api.imunify360.com z.el8z/var/imunify360/tmpzimunify360-merged.configz&imunify360-merged-nonprivileged.configzimunify360.configz/etc/imunify360rz/etc/sysconfig/imunify360iizimunify360.config.dz.imunify360.backup_configz hooks.yamlzcustom_billing.configz/var/imunify360/hookszimunify360-agentzimunify-antiviruszunified-access-logger.confz#/etc/sysconfig/imunify360/.go_agent<N),rrrPRODUCTANTIVIRUS_MODENAME av_version AV_VERSION core_version CORE_VERSION_versionVERSIONr[environrs API_BASE_URLDEFAULT_SOCKET_TIMEOUTDIST FILE_UMASKTMPDIRMERGED_CONFIG_FILE_NAME%MERGED_NONPRIVILEGED_CONFIG_FILE_NAMEUSER_CONFIG_FILE_NAMELOCAL_CONFIG_FILE_NAME CONFIG_DIRr\r] USER_CONFDIRGLOBAL_CONFDIRMERGED_CONFIG_FILE_PATH%MERGED_NONPRIVILEGED_CONFIG_FILE_PATHMERGED_CONFIG_FILE_PERMISSION+MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSIONLOCAL_CONFIG_FILE_PATH CONFIG_D_NAMEBACKUP_CONFIGFILENAMEHOOKS_CONFIGFILENAMECUSTOM_BILLING_CONFIGFILENAMEINBOX_HOOKS_DIRSVC_NAME$UNIFIED_ACCESS_LOGGER_CONFIGFILENAMEr GO_FLAG_FILE%SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECSrrcrGrr sVG'5 N:  ;NDJLG:>>:L  DJ "F80*00"J7<< M::L0N gll/-/GLL=--)%*!27/W\\.:MNN)M7'$;!-O#1I6I ,H(4=>>L,.)))rcrc eZdZe ddededefdZe ddededed ed edd f d Zedd Z e dded edefdZ ede e defdZ dede edefdZdede ededd fdZd S)IConfigTF normalize force_readr@ctrZNotImplementedErrorrrrs rGrrzIConfig.config_to_dict7 "!rcdatavalidate overwritewithout_defaultsNctrZrrrrrrrs rGdict_to_configzIConfig.dict_to_config=s "!rcctrZrrs rGrzIConfig.validateH!!rcconfigctrZrrrrs rGrzIConfig.normalizeLrrc timestampctrZrrrs rGmodified_sincezIConfig.modified_sinceRrrcrxryc|r;||i|SdSrZ)rrrs)rrxrys rGrsz IConfig.getVs@  F&&((,,Wb99==fEE Etrcvaluec@|r||||iidSdSrZ)r)rrxryrs rGsetz IConfig.set[s7  <   65/ : ; ; ; ; ; < """26" """^" !& """" "  "  " """^""""^"8="""15" """^" ""D"""^"3 # <3< z;Normalizer._get_schema_without_defaults..xs]   U777 %&&11%888777rcitems)r)r"s` rGrz'Normalizer._get_schema_without_defaultsvs8    $kkmm     rcrci}|D]W\}}t|tr6|D] \}}||||i|<!P|r|||<X|SrZ)r,r&r setdefault)r new_configrxoptionsryrs rG remove_nullzNormalizer.remove_nulls:< &  . . GW'4(( .%,]]__KKMFE(EJ --gr::6BK .'. 7#rcct|}||}|jrt|j|td||S)NzCerberus returned None for )ConfigValidator normalizederrorsr)rr validatorr4s rG_normalize_with_schemaz!Normalizer._normalize_with_schemasa#F++ %.%9%9&%A%A   :' (899 9  '(Nf(N(NOO Orcrc|r|jn|j}|r+||}|||S||jkr|jS|||}||_||_|jSrZ)r rr1r7rr)rrrrr4s rGrzNormalizer.normalizes-= OD ) )4<   ?%%f--F..vv>> > T\ ! !* *00@@  ",&&rcN) rrrr classmethodrrr staticmethodr1r7rrrrcrGrrks     T   [  G    \ w4\ ' '4 'D ' ' ' ' ' 'rcrc eZdZdZddddddddededeeegeffded e d e f fd Z d Z ddede fdZdde de fdZ d dede de de de ddf dZdZdZdZdeede fdZxZS)!ConfigNT)r\ config_readerr! disclaimercached permissionsr\r>r!r?r@rAct|s|sJ|rtnt}|p|||p|j||_|jj|_|pt|_t|j|_ dS)N)r?rA) superrrr DISCLAIMER_config_readerr\rr!r _normalizer) rr\r>r!r?r@rAconfig_reader_cls __class__s rGrzConfig.__init__s $}$$$28J..l+ /@/@ !4T_#0 0 0  ', !2!H6H%d&<==rccZd|jj|j|jS)NzW<{classname}(config_reader={config_reader!r}, validation_schema={validation_schema!r})>) classnamer>r!)rErHrrEr!rs rG__repr__zConfig.__repr__s6  &n1-"4    rcFrr@c8|j||SrZ)rFrrs rGrzConfig.normalizes))&2BCCCrcrc|j|}|r||}t|S)zr Converts config file to dict :return dict: dictionary (key (section) / value (options)) )r)rEread_config_filerr)rrrrs rGrrzConfig.config_to_dictsD $555LL  ,^^F++Frcrrrrrcr|r|||||dS|||||dS)a Converts dict to config file New options will be mixed in with old ones unless overwrite is specified :param dict data: dictionary (key (section) / value (options)) :param bool validate: indicates if we need validation :param bool normalize: normalize config :param overwrite: overwrite existing conf :param without_defaults: do not fill defaults :return: None )rrrrN)_dict_to_config_overwrite_dict_to_configrs rGrzConfig.dict_to_configst(    * *!#!1 +       !#!1 !     rcc|r t||j|r|||}|j|dSNr)rrr!rrEwrite_config_file)rrrrrs rGrPz Config._dict_to_config_overwritesb  D  % %dD,B C C C  K>>$9I>JJD --d33333rcc t|j}t||rW|r t||j|r|||}|j|dSdSrS) rrErNr rrr!rrU)rrrrrrs rGrQzConfig._dict_to_config s$->>@@AA FD ) ) : J ))&$2HIII -=(   1 1& 9 9 9 9 9 : :rcc |jd}nB#t$r5}d}td||t ||i|d}~wwxYw t ||jdS#t$r5}d}td||t ||i|d}~wwxYw)z/ :raises ConfigsValidatorError F) ignore_errorszError during config validationz%s: %sNz+Imunify360 config does not match the scheme) rErNrrterrorConfigsValidatorErrorrrr!r)r config_dictrFmessages rGrzConfig.validates @->>#?KK @ @ @6G LL7A . . .'w88a ? @  @  % %k43I J J J J J$ @ @ @CG LL7A . . .'w88a ? @s, A0AA! B C 0B==Crc6|j|SrZ)rErrs rGrzConfig.modified_since("11)<<>> > $ > !(2w;*?!?@ >>>>>>>>>.    DDDDDDDD     $     !& !!!! !  !  ! !!!!F444 : : :@@@(==D========rcr<) metaclassceZdZfdZxZS) UserConfigc||_tjtj|tj}t|t||tdS)N)r\r>r!) rnr[r\r]rrrrCrrr)rrnr\rHs rGrzUserConfig.__init__-sl  w||  x)C   *4::4      rcrrrrr_r`s@rGrcrc,s8          rcrcc eZdZdddddededeffdZ dd ed ed efd Z dd eded ededed df dZ ddZ ddeded efdZ de e d efdZxZS) SystemConfigN) local_config merged_confignonpriv_merged_configrhrirjct|p t|_|p t |_|p t |_dSrZ)rCr LocalConfig _local_config MergedConfig_merged_configMergedNonPrivilegedConfig_nonpriv_merged_config)rrhrirjrHs rGrzSystemConfig.__init__:sZ ):[]]+=|~~ ! @%>%@%@ ###rcTFrrr@c:|j||S)Nrr)rorrrs rGrrzSystemConfig.config_to_dictHs)"11J2   rcrrrrcD|j|||||dSN)rrrr)rmrrs rGrzSystemConfig.dict_to_configOs= )) - *     rccj|j|jdSrZ)rorrqrs rGrzSystemConfig.validate_s2 $$&&& #,,.....rcrc:|j||S)N)rr)rorrs rGrzSystemConfig.normalizecs*",,,<-   rcrc6|j|SrZ)rorrs rGrzSystemConfig.modified_sincejr^rcr TTFTr r)rrrr<rrrrrrrrrrrrr_r`s@rGrgrg9sw $ $(,        &       :?   26     !%                //// 9>   15     ==D========rcrgr\c|r%t|tst|S|rt|St S)Nrpr\)r&rBrcr<rg)rnr\s rGconfig_file_factoryr|nsQ 8S118,,,, 4    ~~rcctt}|jD]}||rdSdS)NTF)Mergerget_layer_nameslayersr)rmergerlayers rGany_layer_modified_sincerzsS F**,, - -F    * * 44  5rc)r\r?rA)r>c eZdZdZejddddddedeee geffde ffdZ dd ed e d e d e de ddf fd Z xZS)rlzt Config (/etc/sysconfig/imunify360/imunify360.config) should contain options changed by a customer only NFr\r>r!r?r@r>r!r?cTt|||||dS)NrrCr)rr\r>r!r?r@rHs rGrzLocalConfig.__init__s? '/!      rcTrrrrrr@cPt|||||Sru)rCr)rrrrrrrHs rGrzLocalConfig.dict_to_configs5ww%% - &   rcry)rrrrrrrrrr rrrrr_r`s@rGrlrls (&*CG   $  !(2w;*?!?@         (!%                      rcrlceZdZejejejZ dZ d dZ e dZ d dZe defdZdS) BaseMergerz90-local.configFc<|_fd|D_dS)Ncvg|]5}ttjj|6S)r{)r<r[r\r]DIR)r'r#rs rG z'BaseMerger.__init__..sC   :>F TXt44 5 5 5   rc)_include_defaultsr)rnamesinclude_defaultss` rGrzBaseMerger.__init__s9!1    BG    rcctj|jr&t tj|jngSrZ)r[r\isdirrsortedlistdirr)s rGrzBaseMerger.get_layer_namess6.0gmmCG.D.DLvbj))***"Lrccg}|jr>ttid}|||fd|jDz }||S)NFrTc>g|]}|dS)Frs)rr)r'rrs rGrz.BaseMerger.configs_to_dict..s<     5Z H H   rc)rrrrappendr_build_effective_config)rrlayer_dict_listdefaultss ` rGconfigs_to_dictzBaseMerger.configs_to_dicts  ! -!"455??U@H  " "8 , , ,        ++O<< ? ?C)    MM[M = = = = d   [   rcrc$eZdZdeffd ZxZS) MutableMergerrct||j}|d|}t|ddSNTr)rrrCrrridxrHs rGrzMutableMerger.__init__sE%!788dsd  66666rcrrrrrr_r`s@rGrrsD7h7777777777rcrc$eZdZdeffd ZxZS)ImmutableMergerrct||j}||d}t|ddSNFr)rrrCrrs rGrzImmutableMerger.__init__sE5$"899cdd  77777rcrr`s@rGrrsD8h8888888888rcrc$eZdZdeffd ZxZS) NonBaseMergerrcNt|ddSrrrrrHs rGrzNonBaseMerger.__init__s& 77777rcrr`s@rGrrsD8h8888888888rcrcneZdZddgddgdgdgdZfdZed Zed ed efd Z xZ S) r~Nuser_override_proactive_defensenum_dayslimitenableenable_scan_modsec)PROACTIVE_DEFENCE PERMISSIONSINCIDENT_LOGGINGERROR_REPORTINGMALWARE_SCANNINGcNt|ddSrrrs rGrzMerger.__init__s& 66666rcc||}|}|jD]I}tj|js#t d|jdSJ t |tt}| |d}| |\}}t|dt!|dddS#t"t$f$r&}t d|Yd}~dSd}~wwxYw)NzGAborting merged config update: Config layer %s disappeared during mergeFrT)r)rrzConfig file is invalid! %s)rrrr[r\lexistsrtwarningrrrrr_split_settingsrnrrprZr)r)rr[r normalizer priv_dict nonpriv_dictrFs rGupdate_merged_configzMerger.update_merged_configsS((**++,,.. ]  E7??5:.. ?J      % %k 2 2 2 $$677J$..e/K'*&9&9+&F&F #I| NN ) ))e ) D D D % ' ' 6 6u 7     &'<= < < < NN7 ; ; ; ; ; ; ; ; ; s[)) (* # : @ @ B B   GWk))(0W1E(F(F W%%%FW!555",6646L18CG8L"9 W-f5 ,&&rc) rrrrrr9rrtuplerr_r`s@rGr~r~s! -     ! &77777))[)V'$'5'''['''''rcr~c,eZdZeddfd ZdZxZS)r3T allow_unknownpurge_readonlyc@tj|||d|dS)z Initialises ConfigValidator(Validator) for more details on Validator params please check https://docs.python-cerberus.org/en/stable/validation-rules.html rNr)rrrargskwargsrHs rGrzConfigValidator.__init__[sB  ')        rcch|jdiddrdS|S)Nr&rFT) root_documentrs)rrs rG(_normalize_coerce_user_override_pd_rulesz8ConfigValidator._normalize_coerce_user_override_pd_rulesns7   ! !, 3 3 7 7% H H 4 rc)rrrrrrr_r`s@rGr3r3ZsY%        &rcr3c"eZdZdejzZdS) Packagingz/opt/imunify360/venv/share/%sN)rrrrrDATADIRrrcrGrrts- /var/imunify360/files/geo/v1/GeoLite2-Country-Locations-en.csvc,d|S)Nz2/var/imunify360/files/geo/v1/CountrySubnets-{}.txt)rE) country_codes rGcountry_subnets_filez CountryInfo.country_subnets_filesCJJ    rcN)rrrDB LOCATIONS_DBr:rrrcrGrrs? =B I  \   rcrcjeZdZejdedejzZe ddZ dS)SentryIMUNITY360_SENTRY_DSNz %s/sentryrrN) rrrr[getenvrmrrDSNrENABLErrcrGrrsL ")-L!M!M  CZ)8 4 4FFFrcrceZdZdZdZdZdZdZeddZ eddZ eddZ edd Z edd Z edd Zedd Zedd ZdZdZdZeddZeddZeddZeddZeddZeddZeddZeddZeddZeddZedZeddZ d S)!Malwarei,rrrrmax_targets_per_scan_type max_path_lenenable_scan_inotifyenable_scan_pure_ftpdsends_file_for_analysiscloud_assisted_scan rapid_scancrontabsz$/var/imunify360/aibolit/scans.pickleiz/var/imunify360/cleanup_storageMALWARE_CLEANUPtrim_file_instead_of_removalrxrykeep_original_files_daysscan_modified_filesmax_signature_size_to_scanmax_cloudscan_size_to_scanmax_mrs_upload_file,rapid_scan_rescan_unchanging_files_frequency hyperscanMALWARE_DATABASE_SCANrenable_scan_cpaneldisable_cloudav db_timeoutN)!rrrSCAN_CHECK_PERIODCONSECUTIVE_ERROR_LIMITINOTIFY_SCAN_PERIODCONFIG_CHECK_PERIODCONFLICTS_CHECK_PERIODrMAX_TARGETS_PER_SCAN_TYPE MAX_PATH_LENINOTIFY_ENABLED PURE_SCAN SEND_FILESCLOUD_ASSISTED_SCAN RAPID_SCANCRONTABS_SCAN_ENABLED SCANS_PATHFILE_PREVIEW_BYTES_NUMCLEANUP_STORAGE CLEANUP_TRIM CLEANUP_KEEPSCAN_MODIFIED_FILESMAX_SIGNATURE_SIZE_TO_SCANMAX_CLOUDSCAN_SIZE_TO_SCANMAX_MRS_UPLOAD_FILE,RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY HYPERSCANDATABASE_SCAN_ENABLEDCPANEL_SCAN_ENABLEDrCLEANUP_DISABLE_CLOUDAVMDS_DB_TIMEOUTrrcrGr r s   * 7!!:0.AAL j!35JKKO -/FGGI.0IJJJ$*%79NOO. ==J&J'9:FF7J'7O:!-L:!)L%*"$ ",8""",8""%*%79NOO3=:J440 -{;;I&J'>II$*%79MNN*l+<==Z 7FFNNNrcr cpeZdZdZedZedZededZedZ dS) MalwareTunezc Experimental and testing-only purpose settings we don't want to expose to customers. use_jsonno_check_known_hashesrapid_scan_basedir_overridez/homerno_auto_upgradeN) rrrrrUSE_JSON_REPORTNO_CHECK_KNOWN_HASHESr RAPID_SCAN_BASEDIR_OVERRIDENO_AUTO_UPGRADErrcrGr=r=sj #l:..O(L)@AA".,%dG####l#455OOOrcr=ceZdZeZeZeZeZdS)MalwareScanScheduleIntervalN)rrrNONEDAYWEEKMONTHrrcrGrGrGs" D C D EEErcrGceZdZdZdZdZeddZeddZeddZ edd Z d S) MalwareScanSchedulez8/usr/bin/imunify360-agent malware user scan --backgroundz!/etc/cron.d/imunify_scan_schedulezS# DO NOT EDIT. AUTOMATICALLY GENERATED. 0 {0} {1} * {2} root {cmd} >/dev/null 2>&1 MALWARE_SCAN_SCHEDULEintervalrhour day_of_week day_of_monthN) rrrCMD CRON_PATH CRON_STRINGrINTERVALHOUR DAY_OF_WEEK DAY_OF_MONTHrrcrGrMrMs DC3IK z'H :'   D*'K:'LLLrcrMceZdZeddZeddZeddZeddZeddZeddZ edd Z d S) MalwareScanIntensityMALWARE_SCAN_INTENSITYcpurioram user_scan_cpu user_scan_io user_scan_ram resident_ramN) rrrrCPUIORAMUSER_CPUUSER_IOUSER_RAM RESIDENT_RAMrrcrGr[r[(s *(   C (   B *(   Cz(Hj(Gz(H:(LLLrcr[c\eZdZeddZeddZeddZdS)FileBasedResourceLimitsRESOURCE_MANAGEMENT cpu_limitrio_limit ram_limitN)rrrrrdrerfrrcrGrlrlGsf *%   C %   B *%   CCCrcrlc(eZdZeddZdS) KernelCare KERNELCAREedfrN)rrrrEDFrrcrGrrrrVs* *   CCCrcrrctj}|Rtjdtjdtjdtjdi}|tj dS|S)Nr2r.rr) r r6rGrHrKrJrIrsrMrV)rfreqs rGget_rapid_rescan_frequencyrx]sU  @E } ' ,a ' -q ' ,a ' +R   xx+4a888 LrcceZdZdZejedZejedZejeddZ ejeddZ ejeddZ ejeddZ ejedd Z d S) MalwareSignaturesz/var/imunify360/files/sigs/v1/rfxni360aibolitzai-bolit-hoster-full.dbrzmds-ai-bolit-hoster.dbz procu2.dbz mds-procu2.dbN)rrr_dirr[r\r]RFXNr|AI_BOLIT_HOSTERAI_BOLIT_HYPERSCANMDS_AI_BOLIT_HOSTERPROCU_DB MDS_PROCU_DBrrcrGrzrzjs +D 7<<f % %D 7<<f % %Dgll44MNNOdI{CC',, i1w||D)[99H7<<iAALLLrcrzcJeZdZeddZeddZdZdZdS)LoggerLOGGERmax_log_file_sizer backup_countiiN)rrrrMAX_LOG_FILE_SIZE BACKUP_COUNT LOG_DIR_PERM LOG_FILE_PERMrrcrGrrxsU" ":L LMMMrcrceZdZdZdZdS)rvrnon_rootN)rrrrwNON_ROOTrrcrGrvrvs DHHHrcrv caller_type)r$ceZdZdZdZdS)UIRoleclientadminN)rrrCLIENTADMINrrcrGrrs F EEErcrceZdZdZdZdS)NoCPz/etc/imunify360/scripts/domainsr2N)rrr CLIENT_SCRIPTLATEST_VERSIONrrcrGrrs5MNNNrcrceZdZfdZxZS)CustomBillingConfigctjdtj}t |tdS)Nrr\r!)r[r\r]rrrCrCONFIG_SCHEMA_CUSTOM_BILLING)rr\rHs rGrzCustomBillingConfig.__init__sSw|| ')K   )E      rcrer`s@rGrrs8         rcrc~eZdZeddeZeddeZeddeZeddeZdS) CustomBillingrrrxryrrrrN) rrrrr UPGRADE_URLUPGRADE_URL_360 NOTIFICATIONS IP_LICENSErrcrGrrs* &K !j  &O J &&M  &JJJrcrceZdZeddZeddZeddZeddZeddZeddZ edd Z d S) PermissionsConfigruser_ignore_listrallow_malware_scanuser_override_malware_actionsr*allow_local_malware_ignore_list_managementuse_plesk_service_planallow_wp_waf_rules_managementN) rrrrUSER_IGNORE_LISTALLOW_MALWARE_SCANUSER_OVERRIDE_MALWARE_ACTIONSUSER_OVERRIDE_PROACTIVE_DEFENSE*ALLOW_LOCAL_MALWARE_IGNORE_LIST_MANAGEMENTUSE_PLESK_SERVICE_PLANALLOW_WP_WAF_RULES_MANAGEMENTrrcrGrrs!z!$#%/J&E%%%!'1j0'''#2<;222.(Z'%/J.%%%!!!rcrcBeZdZeddZeddZdS)MyImunifyConfigr&rrpurchase_page_urlN)rrrrENABLEDPURCHASE_PAGE_URLrrcrGrrsKjG# "rcrcBeZdZeddZeddZdS)ControlPanelConfig CONTROL_PANELsmart_advice_allowedradvice_email_notificationN)rrrrSMART_ADVICE_ALLOWEDADVICE_EMAIL_NOTIFICATIONrrcrGrrsL%:%!+ *!!!rcrcgd}tjtjtjtjtjtjd||diddd<fdfd|D}t ||jS) N) BACKUP_RESTORErrrrrr&rrN WORDPRESS))rNrO)rNrP)rNrQ)rNrR)rdefault_action)rmoder waf_enabledT)rrc@t|i}t |i}i}|D]?\}}||}|||fdr|||<:|||<@|S)NT)rrsr,) rx admin_options user_optionsresulting_dictry admin_valuer| admin_dict overridable user_dicts rGnormalize_sectionz0effective_user_config..normalize_section!s !.2s407""7++rc)rrrrrrsfm_config_cleanuprn) admin_configrzallowed_sectionseffective_configrrrrs @@@@rGeffective_user_configrs      :  :  :  :  :  <1K4,,..J**,,I1;R11 c-,-";K -{/C D DDrcc:eZdZdxZ\ZZZZZeeeefZ e re neZ dS) HookEvents)agentlicensezmalware-scanningzmalware-cleanupzmalware-detectedN) rrr IM360_EVENTSAGENTLICENSErrMALWARE_DETECTED IMAV_EVENTSrEVENTSrrcrGrr9sS  L   K + <[[ FFFrcrc0eZdZdeeeffdZdZdS)rZconfigs_to_errorsc||_dSrZ)r)rrs rGrzConfigsValidatorError.__init__Qs!2rccg}|jD]\}}||d| d|S)Nz:  )rr,rr])rr5rrYs rGrKzConfigsValidatorError.__repr__Ts^!399;; 2 2MFE MMV0000 1 1 1 1yy   rcN)rrrrr<rrrKrrcrGrZrZPsG3$vs{*;3333!!!!!rcrZceZdZdZedZedZeefdede ee fddfdZ e de e e fde fd ZdS) rz@A class that has methods to validate configs bypassing the cachecFtdS)zN Validate merged config :raises ConfigsValidatorError N)rgrrs rGvalidate_system_configz'ConfigsValidator.validate_system_config^s !!!!!rcci}ttjD]H} |#t$r$}||jYd}~Ad}~wwxYw|rt |dS)zf Validate all config layers, collect all errors :raises ConfigsValidatorError N)r~rrrrZupdater)r)rrrFs rGvalidate_config_layersz'ConfigsValidator.validate_config_layersfs F224455< > >E >    ( > > >!(()<======== >  ;'(9:: : ; ;sA A4A//A4r[r!r@Nc||}t|}||st|jdS)z Validate config represented by a dict :param config_dict: config to validate :param validation_schema: schema to validate config against :raises ConfigValidationError N)rr3rrr5)r)r[r!rvs rGrzConfigsValidator.validatevsS**+<== F # #zz+&& 2'11 1 2 2rcc8t|r |S|SrZ)callable)r!s rGrz&ConfigsValidator.get_validation_schemas* % & & '$$&& &  rc)rrrrr9rrrrrr rr:rrrrcrGrr[sJJ""[" ; ;[ ;4F222!x02  222[2"! (!23! !!!\!!!rcrc(eZdZeddZdS) AdminContactsADMIN_CONTACTSenable_icontact_notificationsrN)rrrrENABLE_ICONTACT_NOTIFICATIONSrrcrGrrs-$.J .%%%!!!rcrc eZdZdZdZdZdZdS)IContactMessageType MalwareFoundScanNotScheduledGenericc|jSrZ)rrs rG__str__zIContactMessageType.__str__s zrcN)rrr MALWARE_FOUNDSCAN_NOT_SCHEDULEDGENERICrrrcrGrrs3"M+Grcr BACKUP_SYSTEMF)rr$rallowed)enabled backup_systemcfeZdZdZejdeje dfd Z xZ S) BackupConfigaW# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # DO NOT EDIT. AUTOMATICALLY GENERATED. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # Direct modifications to this file WILL be lost upon subsequent # regeneration of this configuration file. # # To have your modifications retained, you should use CLI command # imunify360-agent backup-systems # or activate/deactivate appropriate feature in UI. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # rrcNt||dS)Nrr)rr\r!rHs rGrzBackupConfig.__init__s) d6GHHHHHrc) rrrrDr[r\r]rrCONFIG_SCHEMA_BACKUP_SYSTEMrr_r`s@rGrrsvJ(W\\ ')C  6 IIIIIIIIIIIrcrceZdZeddeZeddeZeddZeddZe d Z d S) BackupRestorerrrrrcl_backup_allowedrcl_on_premise_backup_allowedc*t|jSrZ)_get_backend_system_BACKUP_SYSTEMrs rGrzBackupRestore.backup_systems"3#5666rcN) rrrrrrrCL_BACKUP_ALLOWEDCL_ON_PREMISE_BACKUP_ALLOWEDr9rrrcrGr r sj lG ZN #  "$.: -$$$ 77[777rcr ctddlm}|ttfvrt}n|dS||dS)zo Get backup module from its name :param name: backup system name :return: backup system module r)backup_backendsNT)async_)restore_infectedr CLOUDLINUXCLOUDLINUX_ON_PREMISEACRONISbackend)r#rs rGr r sQ 100000 1222 t  " "4 " 5 55rcceZdZdZdZdhZdS) AcronisBackupzacronis-installer.log)i ii)iZixN)rrrLOG_NAMEPORTSRANGErrcrGrrs!'H E NEEErcrcFtdd|\}}tjo|S)zs Checks is Agent should try restore malware file firts and returns user that set this action in config rtry_restore_from_backup_first)r~r r)rn try_restore_s rG should_try_autorestore_maliciousr"s/ .;XNK  0[0rccvtdd|\}}tjt|z }|S)Nrmax_days_in_backup)days)r~rnowr )rnmax_daysr!untils rG"choose_use_backups_start_from_dater)s?*.KHa LNNYH555 5E Lrcc0tdd|\}}|S)Nrgeneric_user_notificationsrp)r~)rn should_sendr!s rGshould_send_user_notificationsr-"s+-$NK rcceZdZeddZeddZeddZeddZeddZdS) Wordpressrsecurity_plugin_enabledr waf_defaultai_bot_protectionai_bot_protection_presetN) rrrrSECURITY_PLUGIN_ENABLED WAF_ENABLED WAF_DEFAULTAI_BOT_PROTECTIONAI_BOT_PROTECTION_PRESETrrcrGr/r/+sw(j.*[-88K*[-88K" ;0CDD)z/  rcr/ceZdZdZdZdZdZdS) HackerTraprzmalware_found_b64.listzmalware_standalone_b64.listz%/opt/imunify360/proactive/dangerlist/N)rrrrrSA_NAMEDIR_PDrrcrGr:r:7s" C #D+G 4FFFrcr:rZr)r functoolsloggingr[abcrbisectrr contextvarsrcopyrrr enumr pathlibr typingr r rrrrrrrrrcerberusr)defence360agent.contracts.config_providerrrrrr+defence360agent.feature_management.checkersrrdefence360agent._versionrrdefence360agent.utilsrr r!rsr getLoggerrrtrrrMY_IMUNIFY_KEYrrerrNOTIFYCLEANUPrHrIrJrKDEFAULT_INTENSITY_CPUDEFAULT_INTENSITY_IODEFAULT_INTENSITY_RAMDEFAULT_INTENSITY_RESIDENT_RAM%DEFAULT_RESOURCE_MANAGEMENT_CPU_LIMIT$DEFAULT_RESOURCE_MANAGEMENT_IO_LIMIT%DEFAULT_RESOURCE_MANAGEMENT_RAM_LIMITMODSEC_RULESET_FULLMODSEC_RULESET_MINIMAL_DOS_DETECTOR_DEFAULT_LIMIT_DOS_DETECTOR_MIN_LIMIT_DOS_DETECTOR_MIN_INTERVALPORT_BLOCKING_MODE_DENYPORT_BLOCKING_MODE_ALLOWDO_NOT_MODIFY_DISCLAMERDEFAULT_CONFIG_DISCLAMERCPANELPLESK DIRECTADMINrR1SOFT CLUSTERLOGICSSAMPLE_BACKENDrrGENERIC_SENSOR_SOCKET_PATHrrBrHrrXrbrfrmr~rrrr lru_cacherrr Exceptionrrrrrrr<rcrgr|rpartialrrrnrrrprlrrrrr~r3rrrrrrr r=rGrMr[rlrrrxrzrrvrwrrrrrrrrrrrrZrrqrrrrr r rr"r)r-r/r:rrcrGris ,,,,,,,,""""""((((((((                          A@@@@@GGGGGGGGGG X\    8 $ $$X_W---A 8< -    !TJNN">&7c4!%()%'($(+%"! " >{!E$I! !M<>:DDDsDD#DDDD-/J    ")      (<<<MMM-177"Tz7 3d ?7777&111( A A A A A A A A-1Q-- -Q77 7 !  !    /8D%I%I#,>>   ! ,     I   */*/*/*/*/*/*/*/Z'<'<'<'<'sm 4!y  ''  )*6 .I- ''  7*D% % % % % &% % % P((((((((V77777J77788888j88888888J888 \'\'\'\'\'Z\'\'\'~i4========4LLLLLLLL&         555555555G5G5G5G5G5G5G5Gp 6 6 6 6 6 6 6 64>            B B B B B B B B         *z-OOO Z_OOO      &   .<DEDEDEN========.!!!!!I!!!3!3!3!3!3!3!3!3!l! #t"  ! )!"     ,1<IIIII6III:77777777.666"1s1t1111ST        5555555555rcdefence360agent/contracts/__pycache__/config_provider.cpython-311.opt-1.pyc0000644000000000000000000005342600000000000023610 0ustar r_j6ddlZddlZddlZddlZddlmZddlmZddlm Z ddl m Z m Z m Z ddlZddlZddlmZddlmZejeZdZGd d e ZGd d eZGd dZde ede efdZdedefdZdededefdZGddZ Gdde Z!Gdde!Z"Gdd e!Z#dS)!N)abstractmethod)suppress)dedent)MappingOptionalProtocol)atomic_rewrite)open_dir_no_symlinksic~eZdZe d dedefdZededdfd Zed ee defd Z dS) IConfigProviderFT force_read ignore_errorsctNNotImplementedError)selfr rs ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/config_provider.pyread_config_filez IConfigProvider.read_config_files "!configreturnNctrr)rrs rwrite_config_filez!IConfigProvider.write_config_file!!r timestampctrrrrs rmodified_sincezIConfigProvider.modified_since!rrFT) __name__ __module__ __qualname__rboolrrrrfloatrrrr r s>B"""7;"""^" ""D"""^"""D"""^"""rr ceZdZdS) ConfigErrorN)r!r"r#r&rrr(r(&sDrr(ceZdZdZdZdZdS) JsonMessagezPretty-print given *obj* as JSON. To be used for logging. Example: logging.info("object: %s", JsonMessage(obj)) c||_dSr)_obj)robjs r__init__zJsonMessage.__init__3s  rc8tj|jdS)NT) sort_keys)jsondumpsr,rs r__str__zJsonMessage.__str__6sz$)t4444rN)r!r"r#__doc__r.r4r&rrr*r**s<55555rr* prev_sectionsectionc^pipiz }z }fd|Dfd|DfdzDdS)z*Return difference between config sections.c"i|] }|| Sr&r&).0vr6s r z diff_section..As ; ; ;Qaa ; ; ;rc"i|] }|| Sr&r&)r:r;r7s rr<z diff_section..Bs 4 4 4a 4 4 4rcVi|]%}||k|||f&Sr&r&)r:r;r6r7s rr<z diff_section..DsE   A'!*,,  Q,,,,r)-+?keys)r6r7removed_settingsadded_settingss`` r diff_sectionrF:s%2LmG#((**W\\^^;\\^^l&7&7&9&99N ; ; ; ;*: ; ; ; 4 4 4 4^ 4 4 4     "''))GLLNN:     r prev_confconfc#bKz }fd|DVz }fd|DVfdzDVdS)z,Compare *prev_conf* with the current *conf*.c"i|] }|| Sr&r&)r:r7rGs rr<zdiff_config..Os G G G77Ig& G G Grc"i|] }|| Sr&r&)r:r7rHs rr<zdiff_config..Qs @ @ @g7DM @ @ @rcni|]1}||k|t||2Sr&)rF)r:r7rHrGs rr<zdiff_config..SsL     W g . . i0$w-@@ . . .rNrB)rGrHremoved_sectionsadded_sectionss`` r diff_configrOLs ~~''$))++5 G G G G6F G G GGGGYY[[9>>#3#33N @ @ @ @ @ @ @@@@     !((499;;6   r main_conf base_confrct||\}}}i}|D]\}}||vr|||<||vr||i||d||id||dD|S)a Return dict derived from *main_conf* excluding parts that are equal in *base_conf*. For example, >>> base_conf = { "SECTION1": {"OPTION1": "default", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> main_conf = { "SECTION1": {"OPTION1": "value", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> >>> exclude_equals(main_conf=main_conf, base_conf=base_conf) {'SECTION1': {'OPTION1': 'value'}} >>> r@c&i|]\}}||dS)r&)r:kr;s rr<z"exclude_equals..ts"CCCTQAaDCCCrrA)rOitemsrC setdefaultupdate)rPrQ_addedchangedresultr7values rexclude_equalsr^Zs$$Iy99Aug F#//++ ejjll " "#F7O gllnn $ $   gr * * 1 1''2B32G H H H   gr * * 1 1CCWW%5c%:%@%@%B%BCCC    MrceZdZdZddZdZdZ dd ed ed efd Z d e d efdZ dZ dZ d e fdZd e fdZdeed efdZdS) ConfigReaderzM ConfigFile file for settings page. Location config file is PATH Nc0||_||_||_dSr)path disclaimer permissions)rrcrdres rr.zConfigReader.__init__s $&rcNd|jj|jS)Nz<{classname}({path})>) classnamerc)format __class__r#rcr3s r__repr__zConfigReader.__repr__s+&--n1 .   rcd|jS)NzConfigReader at )rcr3s rr4zConfigReader.__str__s-$)---rFTr rrc2 tj|jtkrt d|j}t |d5}t d||}dddn #1swxYwYn/#t$r}t d|d}~wt$ricYSwxYw | |S#t$r*}t ||ricYd}~S|d}~wwxYw)zCRead config file into memory. Raises ConfigError. zConfig file is too largerzReading config file %sNzUnable to decode config file) osrcgetsize_MAX_CONFIG_SIZEr(openloggerinforeadUnicodeDecodeErrorFileNotFoundErrorload_config_bodyerror)rr rfilename config_filetextes rrzConfigReader.read_config_filesu wty)),<<<!"<===yHh$$ *  4h???"'')) * * * * * * * * * * * * * * *" E E E<==1 D    III  ((.. .    LLOOO  G  sfAB0B BBBBB C 'B77C C  C"" D,D DDDr{c  tj|}n+#tj$r}td|d|d}~wwxYw|iSt |t s(td|j||S)Nz.Imunify360 config is not valid YAML document ()z;Imunify360 config is invalid or empty: path={!r}, text={!r})yaml safe_load YAMLErrorr( isinstancedictrhrc)rr{rr|s rrwzConfigReader.load_config_bodys ^D))FF~   EEEE   >I&$'' ))/ 4)@)@   s?:?cdSrr&r3s r _pre_writezConfigReader._pre_write rcdSrr&r3s r _post_writezConfigReader._post_writerrcd}|jr|t|jz }|dz }|tj|dz }|S)Nra F)default_flow_style)rdrrdumprr config_texts r_serialize_configzConfigReader._serialize_configsN ? 6$/22 2K 4 KtyEBBBB rc|||}t|j|d|j||S)NF)backupre)rrr rcrerrs rrzConfigReader.write_config_filesd ,,V44  I{5d>N     rrcdS)NTr&rs rrzConfigReader.modified_sincestrraNr )r!r"r#r5r.rjr4r$rrstrrwrrrrrr%rr&rrr`r`ys# ''''    ...?C7; 4ST&      33Drr`cbeZdZdfd ZdZ ddedeffd Zdd Zd ee d efd Z xZ S)CachedConfigReaderraNct||d|_d|_i|_||_dSr)superr.mtimesize_configrerrcrdreris rr.zCachedConfigReader.__init__s@ z***&* %)  &rcfd|jj|j|j|jS)Nz9{classname} <'{path}', modified at {mtime}, {size} bytes>)rgrcrr)rhrir#rcrrr3s rr4zCachedConfigReader.__str__s7 G N N.5YjY O   rFTr rcH||js|r|j} t||_|jWt t ||j}t|r&tj d|gtt|Rn]#t$rP}tj|td|t|j|s|Yd}~nd}~wwxYw||jS)z(Update config if config file is modified)rNz-%s modified: removed=%s, added=%s, changed=%sz*%s is invalid, using previous settings: %s)rrrrrlistrOanyrrrsmapr*r( sentry_sdkcapture_exceptionwarning_refresh_stat_cache)rr r prev_configdiffsrxris rrz#CachedConfigReader.read_config_filesW   tz * * 'j ',K $ww77"/ 8   :) [$,!G!GHHE5zz K !e44 ,U333@ -- % K      *  $ $ & & &|s'B,, D6ADDrc tj|j}|j|_|j|_dS#t$rd|_d|_YdSwxYw)z-Sync cached mtime/size with the file on disk.N)rnstatrcst_mtimerst_sizerrv)rrs rrz&CachedConfigReader._refresh_stat_cache s] 749%%DDJ DIII    DJDIIII s15AArc|d} tj|j}|j|j}}n#t $rd\}}YnwxYw||kp ||jkS)zWhether the config has updated since *timestamp*. (as defined by its last modification time and size) :param timestamp: None means that the file has never been read before Nr)rr)rnrrcrrrvr)rrrrrs rrz!CachedConfigReader.modified_sincesz  I <749%%D!% t|gHH! ) ) ) ( Hggg ))#;w$)';;s.AArr )rN) r!r"r#r.r4r$rrrr%r __classcell__ris@rrrs''''''   ?C!!!7;!!!!!!F<//`` and the config file inside it must end up owned by ``root:`` with modes ``0750`` / ``0640``. Earlier revisions performed the ``mkdir`` -> ``chown`` -> ``chmod`` sequence on path strings, which left a TOCTOU window: between the directory existing and the metadata syscalls, a swap to a symlink could redirect the chown to an arbitrary inode. See DEF-41586 / CLOS-3965 for context. The hardened path opens the parent ``USER_CONFDIR`` once with ``O_NOFOLLOW`` at every component, then performs every subsequent operation (``mkdir``/``chown``/``chmod``/atomic write) relative to that fd or to a fresh ``O_NOFOLLOW`` fd of the user subdir. No user-controlled path string is dereferenced more than once. iicXt|||_dSr)rr.username)rrcrris rr.zUserConfigReader.__init__Ms&   rcd|jS)NzConfig of user )rr3s rr4zUserConfigReader.__str__Qs0000r parent_fdnamerctt5tj||j|dddn #1swxYwYtj|tjtjztjz|S)aReturn an O_NOFOLLOW fd for ``name`` inside *parent_fd*. Creates the directory first if it does not already exist. The ``O_NOFOLLOW`` flag guarantees that, if a symlink appears in the slot at any time after this call returns, every subsequent ``fchown``/``fchmod``/atomic-rewrite bound to the returned fd operates on the originally opened inode. )modedir_fdNr) rFileExistsErrorrnmkdirDIR_PERMISSIONSrqO_RDONLY O_DIRECTORY O_NOFOLLOW)rrrs r_open_user_subdirz"UserConfigReader._open_user_subdirTso & & H H HT 4Y G G G G H H H H H H H H H H H H H H Hw  K". (2= 8    s>AAc tj|jj}tj|j\}}tj|\}}t|} |||} t j |d|t j ||j | |} t|| dd||j|t j|tjtjz|} t j | d|t j | |jt j| n#t j| wxYw t j|n#t j|wxYw t j|n#t j|wxYw| S)NrF)ruidgidrerr)pwdgetpwnamrpw_gidrnrcsplitr rchownfchmodrrr FILE_PERMISSIONSrqrrclose) rrrconfdirbasename userconfdirrruser_fdrfile_fds rrz"UserConfigReader.write_config_fileesl4=))0GMM$)44 " g 6 6 X )55 * ,,YAAG& "!S))) '4#7888"44V<<   $ 5"'K"-/" &HWa---Igt'<===HW%%%%BHW%%%%%!!!!!!!!! HY    BHY    s=8GBF0E#F#E99F=GF((GG) r!r"r#r5rrr.r4intrrrrrs@rrr8s"O!!!!!111 3 c c    "6366666666rr)$r1loggingrnrabcr contextlibrtextwraprtypingrrrrrdefence360agent.utilsr defence360agent.utils.fd_opsr getLoggerr!rrrpr Exceptionr(r*rrFrOr^r`rrrr&rrrs  .......... 000000======  8 $ $ " " " " "h " " "      )    5 5 5 5 5 5 5 5 x~$ 4 t    $4>XXXXXXXXvN<N<N<N<N<N<N<NB"""7;"""^" ""D"""^"""D"""^"""rr ceZdZdS) ConfigErrorN)r!r"r#r&rrr(r(&sDrr(ceZdZdZdZdZdS) JsonMessagezPretty-print given *obj* as JSON. To be used for logging. Example: logging.info("object: %s", JsonMessage(obj)) c||_dSr)_obj)robjs r__init__zJsonMessage.__init__3s  rc8tj|jdS)NT) sort_keys)jsondumpsr,rs r__str__zJsonMessage.__str__6sz$)t4444rN)r!r"r#__doc__r.r4r&rrr*r**s<55555rr* prev_sectionsectionc^pipiz }z }fd|Dfd|DfdzDdS)z*Return difference between config sections.c"i|] }|| Sr&r&).0vr6s r z diff_section..As ; ; ;Qaa ; ; ;rc"i|] }|| Sr&r&)r:r;r7s rr<z diff_section..Bs 4 4 4a 4 4 4rcVi|]%}||k|||f&Sr&r&)r:r;r6r7s rr<z diff_section..DsE   A'!*,,  Q,,,,r)-+?keys)r6r7removed_settingsadded_settingss`` r diff_sectionrF:s%2LmG#((**W\\^^;\\^^l&7&7&9&99N ; ; ; ;*: ; ; ; 4 4 4 4^ 4 4 4     "''))GLLNN:     r prev_confconfc#bKz }fd|DVz }fd|DVfdzDVdS)z,Compare *prev_conf* with the current *conf*.c"i|] }|| Sr&r&)r:r7rGs rr<zdiff_config..Os G G G77Ig& G G Grc"i|] }|| Sr&r&)r:r7rHs rr<zdiff_config..Qs @ @ @g7DM @ @ @rcni|]1}||k|t||2Sr&)rF)r:r7rHrGs rr<zdiff_config..SsL     W g . . i0$w-@@ . . .rNrB)rGrHremoved_sectionsadded_sectionss`` r diff_configrOLs ~~''$))++5 G G G G6F G G GGGGYY[[9>>#3#33N @ @ @ @ @ @ @@@@     !((499;;6   r main_conf base_confrct||\}}}i}|D]\}}||vr|||<||vr||i||d||id||dD|S)a Return dict derived from *main_conf* excluding parts that are equal in *base_conf*. For example, >>> base_conf = { "SECTION1": {"OPTION1": "default", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> main_conf = { "SECTION1": {"OPTION1": "value", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> >>> exclude_equals(main_conf=main_conf, base_conf=base_conf) {'SECTION1': {'OPTION1': 'value'}} >>> r@c&i|]\}}||dS)r&)r:kr;s rr<z"exclude_equals..ts"CCCTQAaDCCCrrA)rOitemsrC setdefaultupdate)rPrQ_addedchangedresultr7values rexclude_equalsr^Zs$$Iy99Aug F#//++ ejjll " "#F7O gllnn $ $   gr * * 1 1''2B32G H H H   gr * * 1 1CCWW%5c%:%@%@%B%BCCC    MrceZdZdZddZdZdZ dd ed ed efd Z d e d efdZ dZ dZ d e fdZd e fdZdeed efdZdS) ConfigReaderzM ConfigFile file for settings page. Location config file is PATH Nc0||_||_||_dSr)path disclaimer permissions)rrcrdres rr.zConfigReader.__init__s $&rcNd|jj|jS)Nz<{classname}({path})>) classnamerc)format __class__r#rcr3s r__repr__zConfigReader.__repr__s+&--n1 .   rcd|jS)NzConfigReader at )rcr3s rr4zConfigReader.__str__s-$)---rFTr rrc2 tj|jtkrt d|j}t |d5}t d||}dddn #1swxYwYn/#t$r}t d|d}~wt$ricYSwxYw | |S#t$r*}t ||ricYd}~S|d}~wwxYw)zCRead config file into memory. Raises ConfigError. zConfig file is too largerzReading config file %sNzUnable to decode config file) osrcgetsize_MAX_CONFIG_SIZEr(openloggerinforeadUnicodeDecodeErrorFileNotFoundErrorload_config_bodyerror)rr rfilename config_filetextes rrzConfigReader.read_config_filesu wty)),<<<!"<===yHh$$ *  4h???"'')) * * * * * * * * * * * * * * *" E E E<==1 D    III  ((.. .    LLOOO  G  sfAB0B BBBBB C 'B77C C  C"" D,D DDDr{c  tj|}n+#tj$r}td|d|d}~wwxYw|iSt |t s(td|j||S)Nz.Imunify360 config is not valid YAML document ()z;Imunify360 config is invalid or empty: path={!r}, text={!r})yaml safe_load YAMLErrorr( isinstancedictrhrc)rr{rr|s rrwzConfigReader.load_config_bodys ^D))FF~   EEEE   >I&$'' ))/ 4)@)@   s?:?cdSrr&r3s r _pre_writezConfigReader._pre_write rcdSrr&r3s r _post_writezConfigReader._post_writerrcd}|jr|t|jz }|dz }|tj|dz }|S)Nra F)default_flow_style)rdrrdumprr config_texts r_serialize_configzConfigReader._serialize_configsN ? 6$/22 2K 4 KtyEBBBB rc|||}t|j|d|j||S)NF)backupre)rrr rcrerrs rrzConfigReader.write_config_filesd ,,V44  I{5d>N     rrcdS)NTr&rs rrzConfigReader.modified_sincestrraNr )r!r"r#r5r.rjr4r$rrstrrwrrrrrr%rr&rrr`r`ys# ''''    ...?C7; 4ST&      33Drr`cbeZdZdfd ZdZ ddedeffd Zdd Zd ee d efd Z xZ S)CachedConfigReaderraNct||d|_d|_i|_||_dSr)superr.mtimesize_configrerrcrdreris rr.zCachedConfigReader.__init__s@ z***&* %)  &rcfd|jj|j|j|jS)Nz9{classname} <'{path}', modified at {mtime}, {size} bytes>)rgrcrr)rhrir#rcrrr3s rr4zCachedConfigReader.__str__s7 G N N.5YjY O   rFTr rcH||js|r|j} t||_|jWt t ||j}t|r&tj d|gtt|Rn]#t$rP}tj|td|t|j|s|Yd}~nd}~wwxYw||jS)z(Update config if config file is modified)rNz-%s modified: removed=%s, added=%s, changed=%sz*%s is invalid, using previous settings: %s)rrrrrlistrOanyrrrsmapr*r( sentry_sdkcapture_exceptionwarning_refresh_stat_cache)rr r prev_configdiffsrxris rrz#CachedConfigReader.read_config_filesW   tz * * 'j ',K $ww77"/ 8   :) [$,!G!GHHE5zz K !e44 ,U333@ -- % K      *  $ $ & & &|s'B,, D6ADDrc tj|j}|j|_|j|_dS#t$rd|_d|_YdSwxYw)z-Sync cached mtime/size with the file on disk.N)rnstatrcst_mtimerst_sizerrv)rrs rrz&CachedConfigReader._refresh_stat_cache s] 749%%DDJ DIII    DJDIIII s15AArc|d} tj|j}|j|j}}n#t $rd\}}YnwxYw||kp ||jkS)zWhether the config has updated since *timestamp*. (as defined by its last modification time and size) :param timestamp: None means that the file has never been read before Nr)rr)rnrrcrrrvr)rrrrrs rrz!CachedConfigReader.modified_sincesz  I <749%%D!% t|gHH! ) ) ) ( Hggg ))#;w$)';;s.AArr )rN) r!r"r#r.r4r$rrrr%r __classcell__ris@rrrs''''''   ?C!!!7;!!!!!!F<//`` and the config file inside it must end up owned by ``root:`` with modes ``0750`` / ``0640``. Earlier revisions performed the ``mkdir`` -> ``chown`` -> ``chmod`` sequence on path strings, which left a TOCTOU window: between the directory existing and the metadata syscalls, a swap to a symlink could redirect the chown to an arbitrary inode. See DEF-41586 / CLOS-3965 for context. The hardened path opens the parent ``USER_CONFDIR`` once with ``O_NOFOLLOW`` at every component, then performs every subsequent operation (``mkdir``/``chown``/``chmod``/atomic write) relative to that fd or to a fresh ``O_NOFOLLOW`` fd of the user subdir. No user-controlled path string is dereferenced more than once. iicXt|||_dSr)rr.username)rrcrris rr.zUserConfigReader.__init__Ms&   rcd|jS)NzConfig of user )rr3s rr4zUserConfigReader.__str__Qs0000r parent_fdnamerctt5tj||j|dddn #1swxYwYtj|tjtjztjz|S)aReturn an O_NOFOLLOW fd for ``name`` inside *parent_fd*. Creates the directory first if it does not already exist. The ``O_NOFOLLOW`` flag guarantees that, if a symlink appears in the slot at any time after this call returns, every subsequent ``fchown``/``fchmod``/atomic-rewrite bound to the returned fd operates on the originally opened inode. )modedir_fdNr) rFileExistsErrorrnmkdirDIR_PERMISSIONSrqO_RDONLY O_DIRECTORY O_NOFOLLOW)rrrs r_open_user_subdirz"UserConfigReader._open_user_subdirTso & & H H HT 4Y G G G G H H H H H H H H H H H H H H Hw  K". (2= 8    s>AAc tj|jj}tj|j\}}tj|\}}t|} |||} t j |d|t j ||j | |} t|| dd||j|t j|tjtjz|} t j | d|t j | |jt j| n#t j| wxYw t j|n#t j|wxYw t j|n#t j|wxYw| S)NrF)ruidgidrerr)pwdgetpwnamrpw_gidrnrcsplitr rchownfchmodrrr FILE_PERMISSIONSrqrrclose) rrrconfdirbasename userconfdirrruser_fdrfile_fds rrz"UserConfigReader.write_config_fileesl4=))0GMM$)44 " g 6 6 X )55 * ,,YAAG& "!S))) '4#7888"44V<<   $ 5"'K"-/" &HWa---Igt'<===HW%%%%BHW%%%%%!!!!!!!!! HY    BHY    s=8GBF0E#F#E99F=GF((GG) r!r"r#r5rrr.r4intrrrrrs@rrr8s"O!!!!!111 3 c c    "6366666666rr)$r1loggingrnrabcr contextlibrtextwraprtypingrrrrrdefence360agent.utilsr defence360agent.utils.fd_opsr getLoggerr!rrrpr Exceptionr(r*rrFrOr^r`rrrr&rrrs  .......... 000000======  8 $ $ " " " " "h " " "      )    5 5 5 5 5 5 5 5 x~$ 4 t    $4>XXXXXXXXvN<N<N<N<N<N<N<Nzupdate..(s$*r>s!!!!!!;;;;;;FFFFFFFF$! )%%r#  C # #     M4MMMM AAAA AcAAAA 33333 3333333rdefence360agent/contracts/__pycache__/eula.cpython-311.pyc0000644000000000000000000000731100000000000020410 0ustar r_jddlZddlZddlmZddlmZddlmZddl m Z m Z dZ erdndZ d Zd Zdd ed eed efdZded efdZd efdZddZddZd efdZd efdZd efdZdS)N)Optional)files)ANTIVIRUS_MODE)Eularun_in_executorz message{}.txtz-avz eula{}.txtz updated{}.txtpatherrorsreturnct||5}|cdddS#1swxYwYdS)Nr )openreadstrip)r r fs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/eula.py _readfilers d6 " " " avvxx~~                  s&AA  A templatectjtjtj|tSN) osr joinrIndex files_pathEULAformat_SUFFIX)rs r _get_pathrs= 7<< uz**HOOG,D,D  chKttjtjd{VS)z9Return True if latest EULA was accepted, False otherwise.N)rasyncioget_event_loopr is_acceptedrrr#r#s4 !7!9!94;KLL L L L L L LLrclKttjtjd{VdS)z Accepts EULA.N)rr!r"racceptr$rrr&r& s9 '022DK @ @@@@@@@@@@rcZKttjdd{VdS)z$Updates latest EULA date from files.cDtjtS)N)updated)r get_or_creater)r$rrzupdate..(s$*r>s!!!!!!;;;;;;FFFFFFFF$! )%%r#  C # #     M4MMMM AAAA AcAAAA 33333 3333333rdefence360agent/contracts/__pycache__/hook_events.cpython-311.opt-1.pyc0000644000000000000000000001177700000000000022760 0ustar r_j ddlmZddlmZd\ZZGddeZGddeZGdd eZGd d eZ Gd d eZ GddeZ GddZ dS)) HookEvents)Message)startedfinishedceZdZdZdZdZdS)_HookEventBaseNc|d|D}|jjdt|dS)Nc&i|]\}}|dk ||S)DUMP).0kvs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hook_events.py z+_HookEventBase.__repr__..s#AAATQQ&[[Aq[[[())items __class__ __qualname__repr)selffiltereds r__repr__z_HookEventBase.__repr__ s?AATZZ\\AAA.-AAXAAAAr)__name__ __module__reventsubtyperr rrrr s2 EGBBBBBrrceZdZejZdS)_AgentN)rrrrAGENTrr rrr!r!s  EEErr!ceZdZejZdS)_LicenseN)rrrrLICENSErr rrr$r$s  EEErr$ceZdZejZdS)_MalwareScanningN)rrrrMALWARE_SCANNINGrr rrr'r'  'EEErr'ceZdZejZdS)_MalwareDetectedN)rrrrMALWARE_DETECTEDrr rrr+r+r)rr+ceZdZejZdS)_MalwareCleanupN)rrrrMALWARE_CLEANUPrr rrr.r."s  &EEErr.c&eZdZGddeZGddeZGddeZGddeZGd d eZ Gd d e Z Gd de Z Gdde ZGddeZGddeZdS) HookEventceZdZeZdS)HookEvent.AgentStartedNrrrSTARTEDrr rr AgentStartedr3'rr6ceZdZdZdS)HookEvent.AgentMisconfig misconfigNrrrrr rrAgentMisconfigr9*srr<ceZdZdZdS)HookEvent.LicenseExpiredexpiredNr;r rrLicenseExpiredr>-rr@ceZdZdZdS)HookEvent.LicenseExpiringexpiringNr;r rrLicenseExpiringrC0rrEceZdZdZdS)HookEvent.LicenseRenewedrenewedNr;r rrLicenseRenewedrH3rArrJceZdZeZdS) HookEvent.MalwareScanningStartedNr4r rrMalwareScanningStartedrL6r7rrMceZdZeZdS)!HookEvent.MalwareScanningFinishedNrrrFINISHEDrr rrMalwareScanningFinishedrO9rrRceZdZdZdS)!HookEvent.MalwareDetectedCriticalcriticalNr;r rrMalwareDetectedCriticalrU<rFrrWceZdZeZdS)HookEvent.MalwareCleanupStartedNr4r rrMalwareCleanupStartedrY?r7rrZceZdZeZdS) HookEvent.MalwareCleanupFinishedNrPr rrMalwareCleanupFinishedr\BrSrr]N)rrrr!r6r<r$r@rErJr'rMrRr+rWr.rZr]r rrr1r1&sv(!1"2"2rr1N) defence360agent.contracts.configr"defence360agent.contracts.messagesrr5rQrr!r$r'r+r.r1r rrr`sk877777666666)BBBBBWBBB^~(((((~((((((((~((('''''n'''rdefence360agent/contracts/__pycache__/hook_events.cpython-311.pyc0000644000000000000000000001177700000000000022021 0ustar r_j ddlmZddlmZd\ZZGddeZGddeZGdd eZGd d eZ Gd d eZ GddeZ GddZ dS)) HookEvents)Message)startedfinishedceZdZdZdZdZdS)_HookEventBaseNc|d|D}|jjdt|dS)Nc&i|]\}}|dk ||S)DUMP).0kvs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hook_events.py z+_HookEventBase.__repr__..s#AAATQQ&[[Aq[[[())items __class__ __qualname__repr)selffiltereds r__repr__z_HookEventBase.__repr__ s?AATZZ\\AAA.-AAXAAAAr)__name__ __module__reventsubtyperr rrrr s2 EGBBBBBrrceZdZejZdS)_AgentN)rrrrAGENTrr rrr!r!s  EEErr!ceZdZejZdS)_LicenseN)rrrrLICENSErr rrr$r$s  EEErr$ceZdZejZdS)_MalwareScanningN)rrrrMALWARE_SCANNINGrr rrr'r'  'EEErr'ceZdZejZdS)_MalwareDetectedN)rrrrMALWARE_DETECTEDrr rrr+r+r)rr+ceZdZejZdS)_MalwareCleanupN)rrrrMALWARE_CLEANUPrr rrr.r."s  &EEErr.c&eZdZGddeZGddeZGddeZGddeZGd d eZ Gd d e Z Gd de Z Gdde ZGddeZGddeZdS) HookEventceZdZeZdS)HookEvent.AgentStartedNrrrSTARTEDrr rr AgentStartedr3'rr6ceZdZdZdS)HookEvent.AgentMisconfig misconfigNrrrrr rrAgentMisconfigr9*srr<ceZdZdZdS)HookEvent.LicenseExpiredexpiredNr;r rrLicenseExpiredr>-rr@ceZdZdZdS)HookEvent.LicenseExpiringexpiringNr;r rrLicenseExpiringrC0rrEceZdZdZdS)HookEvent.LicenseRenewedrenewedNr;r rrLicenseRenewedrH3rArrJceZdZeZdS) HookEvent.MalwareScanningStartedNr4r rrMalwareScanningStartedrL6r7rrMceZdZeZdS)!HookEvent.MalwareScanningFinishedNrrrFINISHEDrr rrMalwareScanningFinishedrO9rrRceZdZdZdS)!HookEvent.MalwareDetectedCriticalcriticalNr;r rrMalwareDetectedCriticalrU<rFrrWceZdZeZdS)HookEvent.MalwareCleanupStartedNr4r rrMalwareCleanupStartedrY?r7rrZceZdZeZdS) HookEvent.MalwareCleanupFinishedNrPr rrMalwareCleanupFinishedr\BrSrr]N)rrrr!r6r<r$r@rErJr'rMrRr+rWr.rZr]r rrr1r1&sv(!1"2"2rr1N) defence360agent.contracts.configr"defence360agent.contracts.messagesrr5rQrr!r$r'r+r.r1r rrr`sk877777666666)BBBBBWBBB^~(((((~((((((((~((('''''n'''rdefence360agent/contracts/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000002167300000000000021553 0ustar r_jddlZddlZddlmZmZddlmZddlmZGddZ GddeZ Gd d eZ dS) N)ConfigCore) ConfigReader)antivirus_modec eZdZedZeddZeddZedZedZedZ ed Z ed Z ed Z edd Z eddZeddZdS)Schemacd|idS)Ndict)typeschemadefault)datas T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hooks.pyr z Schema.dict s   Nc$dddi|rd|inidgdS)Nlistr stringregexFr r nullabler r)rs rlist_of_stringszSchema.list_of_stringss?',4GU##"   rTcB|rdnd}t|S)Nz^.+@(.+\.)+.+|default$z^.+@(.+\.)+.+$)rr)default_enabledrs rlist_of_emailszSchema.list_of_emailss.*9 O % %>O %%e,,,rcddtdddiS)Nperiodinteger)r coerceminr )intrrrrz Schema.period%s' !   rc d|dS)Nr)r rrrs rrz Schema.string0s    rcddddiS)NenabledbooleanF)r r rrrrr&zSchema.enabled7s! !   rcdtitdti|rtniiS)NADMIN admin_emails)rr r&rrrs radminz Schema.admin@sn V[[nn&&"F$9$9$;$;+18v}}b  rcdtitdtdi|rtniiS)NSCRIPTscriptsz^\/.+$)rr r&rrr+s rscriptz Schema.scriptLsp fkknn&&v55i@@+18v}}b  rcdtit|rtniiS)NUSER)rr r&rr+s ruserz Schema.userXsQ FKKnn&&*08v}}b  rFcltit|SNr+)rr r0r+s r target_scriptzSchema.target_scriptcs3{{ --v-..    rctit|t|Sr5rr r,r0r+s rtarget_admin_and_scriptzSchema.target_admin_and_scriptksI{{ ,,f,-- --v-..    rctit|t|Sr5r8r+s r target_allzSchema.target_alltsK{{ ,,f,-- --v-..    r)N)T)F)__name__ __module__ __qualname__ staticmethodr rrrrr&r,r0r3r6r9r;rrrrr so  \     \  ---\-   \   \   \    \     \    \    \    \    \   rrceZdZdZdZdS)HooksConfigReader_imunifyctj|jdtj|jdt j|jjdS)Nir)oschmodpathchowngrpgetgrnam GROUP_NAMEgr_gid)selfs r _post_writezHooksConfigReader._post_writesA E""" As|DO<<CDDDDDrN)r<r=r>rJrMrrrrArAs-JEEEEErrActeZdZejejejffd Z dZ dZ xZ S) HooksConfigc  tjrttdddddtdddttdttdd dgd ttd ttd tttttd id ntttttttdid}t ||t|dS)NF)rrT)r r rr$)default_emailsnotify_from_emaillocaler)usernameemailsrSrr+)REALTIME_MALWARE_FOUNDUSER_SCAN_MALWARE_FOUNDSCRIPT_BLOCKEDUSER_SCAN_STARTEDCUSTOM_SCAN_STARTEDUSER_SCAN_FINISHEDCUSTOM_SCAN_FINISHEDCUSTOM_SCAN_MALWARE_FOUND)r,usersrulesr )rWrYrZr[r\r])r_r )rFvalidation_schema config_reader) rdisabledrr rrr9r;r6super__init__rA)rLrFr` __class__s rrdzHooksConfig.__init__s`d&]; *0*?*?,1+@++%-'+(,.. #)---">">    #$kk(. u (E(E&,&;&;&=&=&,mmTm&B&B!%!   #::$:GG393D3D3F3F*0*H*H#'+I++.4-A-A-C-C/5/C/C/E/E.4.B.B.D.D060D0D0F0F"::<<$Y- - - ` 393G3G3I3I-3-A-A-C-C/5/C/C/E/E.4.B.B.D.D060D0D0F0F5;5I5I5K5K     a | /+D11      rcZ|}|dd|SNr^)config_to_dictpoprLrs rgetzHooksConfig.gets,""$$ $ rc\|dd||dSrg)ridict_to_configrjs rupdatezHooksConfig.updates0 $ D!!!!!r) r<r=r>rDrFjoinrGLOBAL_CONFDIRHOOKS_CONFIGFILENAMErdrkrn __classcell__)res@rrOrOs{7<< 3T5NOOE E E E E E N """""""rrO) rHrD defence360agent.contracts.configrr)defence360agent.contracts.config_providerrdefence360agent.utilsrrrArOrrrrvs 99999999BBBBBB000000s s s s s s s s lEEEEE EEEO"O"O"O"O"&O"O"O"O"O"rdefence360agent/contracts/__pycache__/hooks.cpython-311.pyc0000644000000000000000000002167300000000000020614 0ustar r_jddlZddlZddlmZmZddlmZddlmZGddZ GddeZ Gd d eZ dS) N)ConfigCore) ConfigReader)antivirus_modec eZdZedZeddZeddZedZedZedZ ed Z ed Z ed Z edd Z eddZeddZdS)Schemacd|idS)Ndict)typeschemadefault)datas T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/hooks.pyr z Schema.dict s   Nc$dddi|rd|inidgdS)Nlistr stringregexFr r nullabler r)rs rlist_of_stringszSchema.list_of_stringss?',4GU##"   rTcB|rdnd}t|S)Nz^.+@(.+\.)+.+|default$z^.+@(.+\.)+.+$)rr)default_enabledrs rlist_of_emailszSchema.list_of_emailss.*9 O % %>O %%e,,,rcddtdddiS)Nperiodinteger)r coerceminr )intrrrrz Schema.period%s' !   rc d|dS)Nr)r rrrs rrz Schema.string0s    rcddddiS)NenabledbooleanF)r r rrrrr&zSchema.enabled7s! !   rcdtitdti|rtniiS)NADMIN admin_emails)rr r&rrrs radminz Schema.admin@sn V[[nn&&"F$9$9$;$;+18v}}b  rcdtitdtdi|rtniiS)NSCRIPTscriptsz^\/.+$)rr r&rrr+s rscriptz Schema.scriptLsp fkknn&&v55i@@+18v}}b  rcdtit|rtniiS)NUSER)rr r&rr+s ruserz Schema.userXsQ FKKnn&&*08v}}b  rFcltit|SNr+)rr r0r+s r target_scriptzSchema.target_scriptcs3{{ --v-..    rctit|t|Sr5rr r,r0r+s rtarget_admin_and_scriptzSchema.target_admin_and_scriptksI{{ ,,f,-- --v-..    rctit|t|Sr5r8r+s r target_allzSchema.target_alltsK{{ ,,f,-- --v-..    r)N)T)F)__name__ __module__ __qualname__ staticmethodr rrrrr&r,r0r3r6r9r;rrrrr so  \     \  ---\-   \   \   \    \     \    \    \    \    \   rrceZdZdZdZdS)HooksConfigReader_imunifyctj|jdtj|jdt j|jjdS)Nir)oschmodpathchowngrpgetgrnam GROUP_NAMEgr_gid)selfs r _post_writezHooksConfigReader._post_writesA E""" As|DO<<CDDDDDrN)r<r=r>rJrMrrrrArAs-JEEEEErrActeZdZejejejffd Z dZ dZ xZ S) HooksConfigc  tjrttdddddtdddttdttdd dgd ttd ttd tttttd id ntttttttdid}t ||t|dS)NF)rrT)r r rr$)default_emailsnotify_from_emaillocaler)usernameemailsrSrr+)REALTIME_MALWARE_FOUNDUSER_SCAN_MALWARE_FOUNDSCRIPT_BLOCKEDUSER_SCAN_STARTEDCUSTOM_SCAN_STARTEDUSER_SCAN_FINISHEDCUSTOM_SCAN_FINISHEDCUSTOM_SCAN_MALWARE_FOUND)r,usersrulesr )rWrYrZr[r\r])r_r )rFvalidation_schema config_reader) rdisabledrr rrr9r;r6super__init__rA)rLrFr` __class__s rrdzHooksConfig.__init__s`d&]; *0*?*?,1+@++%-'+(,.. #)---">">    #$kk(. u (E(E&,&;&;&=&=&,mmTm&B&B!%!   #::$:GG393D3D3F3F*0*H*H#'+I++.4-A-A-C-C/5/C/C/E/E.4.B.B.D.D060D0D0F0F"::<<$Y- - - ` 393G3G3I3I-3-A-A-C-C/5/C/C/E/E.4.B.B.D.D060D0D0F0F5;5I5I5K5K     a | /+D11      rcZ|}|dd|SNr^)config_to_dictpoprLrs rgetzHooksConfig.gets,""$$ $ rc\|dd||dSrg)ridict_to_configrjs rupdatezHooksConfig.updates0 $ D!!!!!r) r<r=r>rDrFjoinrGLOBAL_CONFDIRHOOKS_CONFIGFILENAMErdrkrn __classcell__)res@rrOrOs{7<< 3T5NOOE E E E E E N """""""rrO) rHrD defence360agent.contracts.configrr)defence360agent.contracts.config_providerrdefence360agent.utilsrrrArOrrrrvs 99999999BBBBBB000000s s s s s s s s lEEEEE EEEO"O"O"O"O"&O"O"O"O"O"rdefence360agent/contracts/__pycache__/license.cpython-311.opt-1.pyc0000644000000000000000000007707400000000000022060 0ustar r_jCcddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m Z ddlm Z ddl mZddlmZddlmZddlmZddlmZdd lmZdd lmZmZmZmZmZdd lmZdd l m!Z!dd l"m#Z#ddl$m%Z%m&Z&ddl'm(Z(m)Z)ddl*m+Z+m,Z,ddl-m.Z.dZ/dZ0eddZ1edxZ23s*edxZ23s edZ2e)e(ej4ej5Z6e)e(ej4ej5Z7Gdde8Z9GddZ:dZ;dedS)"N)suppress)JSONDecodeError)Path)TimeoutExpired)Optional)OperationalError)is_cpanel_installed)sentry)ANTIVIRUS_MODECore CustomBillingint_from_envvarlogger) HookEvent)g)get_plesk_upgrade_urls)retry_on timed_cache)HOUR rate_limit)APIError IPEchoAPI)IP IMUNIFYAVi&IMUNIFY360_CACHE_LICENSE_TOKEN_TIMEOUTiXz/opt/alt/openssl11/bin/opensslz/opt/alt/openssl/bin/opensslz/usr/bin/openssl)periodon_dropceZdZdZdS) LicenseErrorz9Used to communicate that some function requires a licenseN)__name__ __module__ __qualname____doc__V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/license.pyrr@sCCCCr%rc.eZdZdZdZeedZdZdZdZdZ dZ d Z d Z d Z gd ZiZd ZeeeddedededeeeeeffdZed8dedefdZedeeeefdeeeeffdZedZee e!j"e#dde$fdZ%edeefdZ&edZ'ed Z(ed!Z)edefd"Z*ed9d#Z+ed9d$efd%Z,ed&Z-ed'Z.ed(Z/ed)Z0ed*Z1ed+Z2ed,eedeefd-Z3ed.Z4ed/Z5ed0Z6edefd1Z7edefd2Z8edefd3Z9edefd4Z:edefd5Z;ed6Zr?FileNotFoundErrorfilename) r6r7r8errorsresultsig_filecmdpes r&_verify_signaturezLicenseCLN._verify_signaturevs  ( 5 5 5  NN9 % % % NN    C N%?%?! <1$$!FFMMB,-LBB#$8BB78xBB % O O O MMMNNNNNNNN O)               @v~%%s;=D-Cz2LicenseCLN._get_signature_input..sH,FH"..H..r%null)VERIFY_FIELDS_MAP isinstancedictrLjoinitemsstrencode)clslicenserVpartskeyvalues r&_get_signature_inputzLicenseCLN._get_signature_inputs(1 ) )CCLE%&& ) GG05   V$$$$ SZZ((((wwu~~$$&&&r%signature_listcF g fd}|D]y\}}tj|} ||}n#t$rY>wxYw|j||r|dfcSjD]}||||r|dfccSz D]} t jd| dS)zc Verify signatures in license :return: signature, is_alternative, version cVj|i|\}}|r||SN)rUextend)argskwargssuccessrO all_errorsrgs r&verify_and_collect_errorsz=LicenseCLN._find_signature..verify_and_collect_errorss?3c3TDVDDOGV *!!&)))Nr%)rVFTz%sNF)base64 b64decoderlKeyError _PUBKEY_FILE_ALTERNATIVE_PUBKEY_FILESrwarning) rg license_tokenrmrvsignrVr8r7 alt_pubkeyerrorrus ` @r&_find_signaturezLicenseCLN._find_signatures6!#        , & &MD'(..I 22!73    )()97INN #U{"""!; & & ,,Z)LL&:%%%%%& &  ( (E N4 ' ' ' '{s? A  A c i} t|5}tj|}t|ts%t jd||cdddS||d|dgD\}}|d}|rD|||dfg\}} |%td| ddn(d|vr$| dtd |td |cdddS||d <||d <|cdddS#1swxYwYn#t$rt j d Ynpt$r} t jd| Yd} ~ nMd} ~ wt t"t$t&jt*f$r} t jd| Yd} ~ nd} ~ wwxYw|S)z Load license token from file and verify signature If signature verification successful, put first valid signature to 'sign' field of license token :return: license token z2Failed to load license. Expected JSON object, got Ncg|]}|dfSr1r$)r[rs r& z*LicenseCLN._load_token..s, q r% signatures signature_v2r2z%Failed to verify license signature v2r0zdLicense missing signature_v2 but contained permissions; stripped (possible tampering or stale token)z"Failed to verify license signatureris_alternativez'Failed to load license: not registered?zFailed to load license: %s)openjsonloadrarbrrrgetthrottled_log_errorpopthrottled_log_no_v2rMinforr}OSErrorrzUnicodeDecodeErrorbinasciiError TypeError) rgpathdefaultfr~r8rv2_sign_sign_rTs r& _load_tokenzLicenseCLN._load_tokens; :d) %q $ ! !-66#LL(=+#) %) %) %) %) %) %) %) %-0,?,?!$1$5$5lB$G$G--) >(++N;;"22%! ~  HE1}+C&))->>>"m33!%%m444'' $'(LMMM"K) %) %) %) %) %) %) %) %N)2 f%2@ ./$S) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %V! C C C KA B B B B B < < < N7 ; ; ; ; ; ; ; ;    N    : : : L5q 9 9 9 9 9 9 9 9 :sfEAE E"C E. E; E EEEEEG,? G,F""+G, G''G,)seconds)maxsizeci}tr|j|jgn|jg}|D]}||}|r|cS|S)z Get available license. In Antivirus mode, if main license is unavailable, return free license :return: license token )r _LICENSE_FILE_FREE_LICENSE_FILEr)rg lic_token license_fileslfs r& get_tokenzLicenseCLN.get_token&sr  %S  6 7 7#$    ! !B++I !     !r%cP|dS)z$ :return: server id r)rrrgs r& get_server_idzLicenseCLN.get_server_id=s }}""4(((r%cDt|S)z1 :return: bool: if we have token )boolrrs r& is_registeredzLicenseCLN.is_registeredDs CMMOO$$$r%cbto(|o| S)ze :return: Return true only if we have valid ImunifyAV+ or Imunify360 license )r is_validis_freers r&is_valid_av_pluszLicenseCLN.is_valid_av_plusKs' H#,,..H#++--6GHr%cNtsdS|tkSrw)r r AV_DEFAULT_IDrs r&rzLicenseCLN.is_freeSs& 5  ""m33r%c<tsdS|S)zCCloud-assisted cleanup is a paid feature (Imunify360 / ImunifyAV+).T)r rrs r&!is_cloud_assisted_cleanup_allowedz,LicenseCLN.is_cloud_assisted_cleanup_allowedYs# 4##%%%r%cR|p|}|sdStrF|dddo|dt jkS|ddvo6|dt jko|jdup|j|dkS) zLicense check based on license token return True - if license token is valid for this server return False - if license token is invalid Fr*rXokr.rok-trialNr,)rr r startswithtime users_countrgtokens r&rzLicenseCLN.is_valid`s( 5   (B''22488=,-<  (O1 1 O()TY[[8 OD(MCOuW~,M r% permissionc|p|}|sdS||dix}vo ||dkS)zLicense check for a specific permission based on a license token return True - if license token has a given permission for this server return False - if license token does not have permission Fr0ENABLEDr)rgrrperms r&has_permissionzLicenseCLN.has_permissionwsW( 5 599]B#?#??4 @ .Z I- r%c~|}|s |d |d|d<|jdz}tjtjztjz}d}tt5tj |dddn #1swxYwYtj tj |||d5}tj ||dddn #1swxYwYtj|dd tj||j|jt%j|t%j| |||dS#t0$rYdSwxYw) zb Write new license token to file :param token: new token :return: r,Nsaved_user_limitz.tmpiwroot_imunify)userr+)rrrosO_WRONLYO_CREATO_EXCLrrMunlinkfdopenrrdumpshutilchownrename cache_clearr set_server_idrset_product_nameget_product_name renew_hookr)rgr old_token temp_fileflagsmoders r&updatezLicenseCLN.updatesMMOO  7UYYw//;(-gE$ %%.  bj(294 ' ( ( ! ! Ii  ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Yrwy%66 < <  IeQ                    YV:>>>> )S./// !!###S..00111 4 4 6 6777  NN9e , , , , ,    DD s6=BB"%B"C44C8;C8F.. F<;F<cgd}d}|}tfd|D}|r=tj||}ddlm}tj||ddSdS) N)license_expire_utcr*r,r)rchg|].}||k/Sr$r)r[elemrrs r&rz)LicenseCLN.renew_hook..s4 O O OUYYt__ d 3 3 3 O O Or%)exp_timerhr) execute_hooksT)return_exceptions) rfill_license_typeanyrLicenseReneweddefence360agent.hooks.executerasynciogather) rgrrimportant_keysr license_type conditionlicense_updatedrs `` r&rzLicenseCLN.renew_hooksHHH99122,,U33  O O O O O O O O    '6!<O D C C C C C N o..$         r%c6tt5tj|jdddn #1swxYwY|jtjdtj | dS)zY Delete license token along with old-style license data :return: N) rrMrrrrrr rrrrs r&r;zLicenseCLN.deletes ' ( ( ) ) Ic' ( ( ( ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) !!###T""" 4 4 6 677777s ;??cd|d}ddddd}||S)Nr* imunify360imunify360Trial imunifyAV imunifyAVPlus)rrok-avok-avpr)rgrrlicense_type_to_products r&rzLicenseCLN.fill_license_typesByy** ) % # #  '**<888r%cP||Srp)rrrs r&get_license_typezLicenseCLN.get_license_types$$S]]__555r%c|}|dddrdSdS)Nr)rXzip-TF)rrlowerrrs r&is_ip_license_typezLicenseCLN.is_ip_license_typesI  99T2   $ $ & & 1 1% 8 8 4ur% url_templatec<|s|S|j}tjdd}|d}n|jD] }||kr|}n d}|dt |}|d|}|dt ||nd}|S) a&Format upgrade URL template with available parameters. Args: url_template: URL template string that may contain {user_count}, {iaid}, and {users} placeholders Returns: Formatted URL with placeholders replaced with actual values iaidrXNr1 unlimitedz {user_count}z{iaid}z{users})rrrVERSION_THRESHOLDSreplacere)rgrnr user_count thresholds r&format_upgrade_urlzLicenseCLN.format_upgrade_urls   OuVR   9JJ 3 ) )  >>!*JE") #++NC OOLL #++Hd;; #++ s 11155  r%c6|dkrdS|dkrdS|dkrdSdS)z1Get recommended license tier based on user count.r1z Single userr3zUp to 30 usersr4zUp to 250 userszUnlimited usersr$)rgrs r& _get_license_tier_recommendationz+LicenseCLN._get_license_tier_recommendations: ?? = 2  ## 3  $$$$r%cb| dS||}|dkrdnd}d|d|d|d S) z ? ? ?  % 5 B-1AA  r%c||o(|o| Srp)r*rr0rs r&r#z(LicenseCLN.is_eligible_for_imunify_patchs; JJLL 8  855777 r%cts tjS|dd}|dkrdS|dvrdSt jd|dS) Nr*rXrz imunify.av)rrrz imunify.av+zUnknown license %szUnknown license)r r NAMErrrr)rglicense_statuss r&rzLicenseCLN.get_product_namesi 9 ,,Xr:: W $ $< ; ; ; = L-~ > > >$$r%c@tjdS)Nz/var/imunify360/demo)rrisfilers r&r"zLicenseCLN.is_demosw~~4555r%ch|}|ddtkS)Nr,r)rrUNLIMITED_USERS_COUNTrs r& is_unlimitedzLicenseCLN.is_unlimiteds) yy!$$(===r%c|j|jdS|jD]*}|j|kr|j|cS+|jdS)Nr1)rr)rIM360_BUY_URL_TEMPLATEformatr)rgrs r&get_im360_buy_urlzLicenseCLN.get_im360_buy_urls ? "-444BB B/ O OI)++188I8NNNNN,)00K0HHHr%rrp)>r r!r"VERIFY_FIELDS_V1VERIFY_FIELDS_V2r`r{r|rrr r;r,r-r_tokenr staticmethodrrrebytestuplerrlistrU classmethodintrlrrrdatetime timedelta_CACHE_LICENSE_TOKEN_TIMEOUTrbrrrrrrrrrrr;rrrrr rr(r*r0r#rr"r9r=r$r%r&r(r(Ds*   7L!3M<C C A >& FK Xn***)&)&#()&5:)& tXd3i(( ))&)&)&+*\)&V''C''''['"%,0sCx,A% x}d" #%%%[%NFF[FP[#?@@@!$[&)hsm)))[) %%[% II[I44[4 &$&&&[&    [ ,       [  [B[$ 8 8[ 899[966[6[  hsm    [ D % %[ %  [ &ZZ[ZxDtDDD[D  d    [   d   [  % % % %[ %6666[6>>[>I#III[IIIr%r(cBtj}tjdd}t rt jtjkrt|s tj Sd}d} tj }tj |r|}ntjd|n,#t $r}tjd|Yd}~nd}~wwxYw|dkrd|}nd|}||zStd |zd |t'|zzS) NrrXz???uuG  sAA98A9)?rrxrrGrrrrHrBr contextlibrrpathlibrrtypingrpeeweer3defence360agent.application.determine_hosting_panelr defence360agent.contractsr defence360agent.contracts.configr r r rr%defence360agent.contracts.hook_eventsr&defence360agent.internals.global_scoper0defence360agent.subsys.panels.plesk.upgrade_urlsrdefence360agent.utilsrrdefence360agent.utils.commonrrdefence360agent.utils.ipechorrdefence360agent.utils.validaterrr8rIrFexistsr}rrr Exceptionrr(r!rerrKr$r%r&ris_    %%%%%%######-,,,,,<;;;;;4444448777777799999999<<<<<<<<------ " /,   t<=== EEGG/4 >?? ?K G G I I/d-.. EjjfnEEE LFjjfnEEE L DDDDD9DDDt It It It It It It It In555p  #  $       r%defence360agent/contracts/__pycache__/license.cpython-311.pyc0000644000000000000000000007707400000000000021121 0ustar r_jCcddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m Z ddlm Z ddl mZddlmZddlmZddlmZddlmZdd lmZdd lmZmZmZmZmZdd lmZdd l m!Z!dd l"m#Z#ddl$m%Z%m&Z&ddl'm(Z(m)Z)ddl*m+Z+m,Z,ddl-m.Z.dZ/dZ0eddZ1edxZ23s*edxZ23s edZ2e)e(ej4ej5Z6e)e(ej4ej5Z7Gdde8Z9GddZ:dZ;dedS)"N)suppress)JSONDecodeError)Path)TimeoutExpired)Optional)OperationalError)is_cpanel_installed)sentry)ANTIVIRUS_MODECore CustomBillingint_from_envvarlogger) HookEvent)g)get_plesk_upgrade_urls)retry_on timed_cache)HOUR rate_limit)APIError IPEchoAPI)IP IMUNIFYAVi&IMUNIFY360_CACHE_LICENSE_TOKEN_TIMEOUTiXz/opt/alt/openssl11/bin/opensslz/opt/alt/openssl/bin/opensslz/usr/bin/openssl)periodon_dropceZdZdZdS) LicenseErrorz9Used to communicate that some function requires a licenseN)__name__ __module__ __qualname____doc__V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/license.pyrr@sCCCCr%rc.eZdZdZdZeedZdZdZdZdZ dZ d Z d Z d Z gd ZiZd ZeeeddedededeeeeeffdZed8dedefdZedeeeefdeeeeffdZedZee e!j"e#dde$fdZ%edeefdZ&edZ'ed Z(ed!Z)edefd"Z*ed9d#Z+ed9d$efd%Z,ed&Z-ed'Z.ed(Z/ed)Z0ed*Z1ed+Z2ed,eedeefd-Z3ed.Z4ed/Z5ed0Z6edefd1Z7edefd2Z8edefd3Z9edefd4Z:edefd5Z;ed6Zr?FileNotFoundErrorfilename) r6r7r8errorsresultsig_filecmdpes r&_verify_signaturezLicenseCLN._verify_signaturevs  ( 5 5 5  NN9 % % % NN    C N%?%?! <1$$!FFMMB,-LBB#$8BB78xBB % O O O MMMNNNNNNNN O)               @v~%%s;=D-Cz2LicenseCLN._get_signature_input..sH,FH"..H..r%null)VERIFY_FIELDS_MAP isinstancedictrLjoinitemsstrencode)clslicenserVpartskeyvalues r&_get_signature_inputzLicenseCLN._get_signature_inputs(1 ) )CCLE%&& ) GG05   V$$$$ SZZ((((wwu~~$$&&&r%signature_listcF g fd}|D]y\}}tj|} ||}n#t$rY>wxYw|j||r|dfcSjD]}||||r|dfccSz D]} t jd| dS)zc Verify signatures in license :return: signature, is_alternative, version cVj|i|\}}|r||SN)rUextend)argskwargssuccessrO all_errorsrgs r&verify_and_collect_errorsz=LicenseCLN._find_signature..verify_and_collect_errorss?3c3TDVDDOGV *!!&)))Nr%)rVFTz%sNF)base64 b64decoderlKeyError _PUBKEY_FILE_ALTERNATIVE_PUBKEY_FILESrwarning) rg license_tokenrmrvsignrVr8r7 alt_pubkeyerrorrus ` @r&_find_signaturezLicenseCLN._find_signatures6!#        , & &MD'(..I 22!73    )()97INN #U{"""!; & & ,,Z)LL&:%%%%%& &  ( (E N4 ' ' ' '{s? A  A c i} t|5}tj|}t|ts%t jd||cdddS||d|dgD\}}|d}|rD|||dfg\}} |%td| ddn(d|vr$| dtd |td |cdddS||d <||d <|cdddS#1swxYwYn#t$rt j d Ynpt$r} t jd| Yd} ~ nMd} ~ wt t"t$t&jt*f$r} t jd| Yd} ~ nd} ~ wwxYw|S)z Load license token from file and verify signature If signature verification successful, put first valid signature to 'sign' field of license token :return: license token z2Failed to load license. Expected JSON object, got Ncg|]}|dfSr1r$)r[rs r& z*LicenseCLN._load_token..s, q r% signatures signature_v2r2z%Failed to verify license signature v2r0zdLicense missing signature_v2 but contained permissions; stripped (possible tampering or stale token)z"Failed to verify license signatureris_alternativez'Failed to load license: not registered?zFailed to load license: %s)openjsonloadrarbrrrgetthrottled_log_errorpopthrottled_log_no_v2rMinforr}OSErrorrzUnicodeDecodeErrorbinasciiError TypeError) rgpathdefaultfr~r8rv2_sign_sign_rTs r& _load_tokenzLicenseCLN._load_tokens; :d) %q $ ! !-66#LL(=+#) %) %) %) %) %) %) %) %-0,?,?!$1$5$5lB$G$G--) >(++N;;"22%! ~  HE1}+C&))->>>"m33!%%m444'' $'(LMMM"K) %) %) %) %) %) %) %) %N)2 f%2@ ./$S) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %) %V! C C C KA B B B B B < < < N7 ; ; ; ; ; ; ; ;    N    : : : L5q 9 9 9 9 9 9 9 9 :sfEAE E"C E. E; E EEEEEG,? G,F""+G, G''G,)seconds)maxsizeci}tr|j|jgn|jg}|D]}||}|r|cS|S)z Get available license. In Antivirus mode, if main license is unavailable, return free license :return: license token )r _LICENSE_FILE_FREE_LICENSE_FILEr)rg lic_token license_fileslfs r& get_tokenzLicenseCLN.get_token&sr  %S  6 7 7#$    ! !B++I !     !r%cP|dS)z$ :return: server id r)rrrgs r& get_server_idzLicenseCLN.get_server_id=s }}""4(((r%cDt|S)z1 :return: bool: if we have token )boolrrs r& is_registeredzLicenseCLN.is_registeredDs CMMOO$$$r%cbto(|o| S)ze :return: Return true only if we have valid ImunifyAV+ or Imunify360 license )r is_validis_freers r&is_valid_av_pluszLicenseCLN.is_valid_av_plusKs' H#,,..H#++--6GHr%cNtsdS|tkSrw)r r AV_DEFAULT_IDrs r&rzLicenseCLN.is_freeSs& 5  ""m33r%c<tsdS|S)zCCloud-assisted cleanup is a paid feature (Imunify360 / ImunifyAV+).T)r rrs r&!is_cloud_assisted_cleanup_allowedz,LicenseCLN.is_cloud_assisted_cleanup_allowedYs# 4##%%%r%cR|p|}|sdStrF|dddo|dt jkS|ddvo6|dt jko|jdup|j|dkS) zLicense check based on license token return True - if license token is valid for this server return False - if license token is invalid Fr*rXokr.rok-trialNr,)rr r startswithtime users_countrgtokens r&rzLicenseCLN.is_valid`s( 5   (B''22488=,-<  (O1 1 O()TY[[8 OD(MCOuW~,M r% permissionc|p|}|sdS||dix}vo ||dkS)zLicense check for a specific permission based on a license token return True - if license token has a given permission for this server return False - if license token does not have permission Fr0ENABLEDr)rgrrperms r&has_permissionzLicenseCLN.has_permissionwsW( 5 599]B#?#??4 @ .Z I- r%c~|}|s |d |d|d<|jdz}tjtjztjz}d}tt5tj |dddn #1swxYwYtj tj |||d5}tj ||dddn #1swxYwYtj|dd tj||j|jt%j|t%j| |||dS#t0$rYdSwxYw) zb Write new license token to file :param token: new token :return: r,Nsaved_user_limitz.tmpiwroot_imunify)userr+)rrrosO_WRONLYO_CREATO_EXCLrrMunlinkfdopenrrdumpshutilchownrename cache_clearr set_server_idrset_product_nameget_product_name renew_hookr)rgr old_token temp_fileflagsmoders r&updatezLicenseCLN.updatesMMOO  7UYYw//;(-gE$ %%.  bj(294 ' ( ( ! ! Ii  ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! Yrwy%66 < <  IeQ                    YV:>>>> )S./// !!###S..00111 4 4 6 6777  NN9e , , , , ,    DD s6=BB"%B"C44C8;C8F.. F<;F<cgd}d}|}tfd|D}|r=tj||}ddlm}tj||ddSdS) N)license_expire_utcr*r,r)rchg|].}||k/Sr$r)r[elemrrs r&rz)LicenseCLN.renew_hook..s4 O O OUYYt__ d 3 3 3 O O Or%)exp_timerhr) execute_hooksT)return_exceptions) rfill_license_typeanyrLicenseReneweddefence360agent.hooks.executerasynciogather) rgrrimportant_keysr license_type conditionlicense_updatedrs `` r&rzLicenseCLN.renew_hooksHHH99122,,U33  O O O O O O O O    '6!<O D C C C C C N o..$         r%c6tt5tj|jdddn #1swxYwY|jtjdtj | dS)zY Delete license token along with old-style license data :return: N) rrMrrrrrr rrrrs r&r;zLicenseCLN.deletes ' ( ( ) ) Ic' ( ( ( ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) !!###T""" 4 4 6 677777s ;??cd|d}ddddd}||S)Nr* imunify360imunify360Trial imunifyAV imunifyAVPlus)rrok-avok-avpr)rgrrlicense_type_to_products r&rzLicenseCLN.fill_license_typesByy** ) % # #  '**<888r%cP||Srp)rrrs r&get_license_typezLicenseCLN.get_license_types$$S]]__555r%c|}|dddrdSdS)Nr)rXzip-TF)rrlowerrrs r&is_ip_license_typezLicenseCLN.is_ip_license_typesI  99T2   $ $ & & 1 1% 8 8 4ur% url_templatec<|s|S|j}tjdd}|d}n|jD] }||kr|}n d}|dt |}|d|}|dt ||nd}|S) a&Format upgrade URL template with available parameters. Args: url_template: URL template string that may contain {user_count}, {iaid}, and {users} placeholders Returns: Formatted URL with placeholders replaced with actual values iaidrXNr1 unlimitedz {user_count}z{iaid}z{users})rrrVERSION_THRESHOLDSreplacere)rgrnr user_count thresholds r&format_upgrade_urlzLicenseCLN.format_upgrade_urls   OuVR   9JJ 3 ) )  >>!*JE") #++NC OOLL #++Hd;; #++ s 11155  r%c6|dkrdS|dkrdS|dkrdSdS)z1Get recommended license tier based on user count.r1z Single userr3zUp to 30 usersr4zUp to 250 userszUnlimited usersr$)rgrs r& _get_license_tier_recommendationz+LicenseCLN._get_license_tier_recommendations: ?? = 2  ## 3  $$$$r%cb| dS||}|dkrdnd}d|d|d|d S) z ? ? ?  % 5 B-1AA  r%c||o(|o| Srp)r*rr0rs r&r#z(LicenseCLN.is_eligible_for_imunify_patchs; JJLL 8  855777 r%cts tjS|dd}|dkrdS|dvrdSt jd|dS) Nr*rXrz imunify.av)rrrz imunify.av+zUnknown license %szUnknown license)r r NAMErrrr)rglicense_statuss r&rzLicenseCLN.get_product_namesi 9 ,,Xr:: W $ $< ; ; ; = L-~ > > >$$r%c@tjdS)Nz/var/imunify360/demo)rrisfilers r&r"zLicenseCLN.is_demosw~~4555r%ch|}|ddtkS)Nr,r)rrUNLIMITED_USERS_COUNTrs r& is_unlimitedzLicenseCLN.is_unlimiteds) yy!$$(===r%c|j|jdS|jD]*}|j|kr|j|cS+|jdS)Nr1)rr)rIM360_BUY_URL_TEMPLATEformatr)rgrs r&get_im360_buy_urlzLicenseCLN.get_im360_buy_urls ? "-444BB B/ O OI)++188I8NNNNN,)00K0HHHr%rrp)>r r!r"VERIFY_FIELDS_V1VERIFY_FIELDS_V2r`r{r|rrr r;r,r-r_tokenr staticmethodrrrebytestuplerrlistrU classmethodintrlrrrdatetime timedelta_CACHE_LICENSE_TOKEN_TIMEOUTrbrrrrrrrrrrr;rrrrr rr(r*r0r#rr"r9r=r$r%r&r(r(Ds*   7L!3M<C C A >& FK Xn***)&)&#()&5:)& tXd3i(( ))&)&)&+*\)&V''C''''['"%,0sCx,A% x}d" #%%%[%NFF[FP[#?@@@!$[&)hsm)))[) %%[% II[I44[4 &$&&&[&    [ ,       [  [B[$ 8 8[ 899[966[6[  hsm    [ D % %[ %  [ &ZZ[ZxDtDDD[D  d    [   d   [  % % % %[ %6666[6>>[>I#III[IIIr%r(cBtj}tjdd}t rt jtjkrt|s tj Sd}d} tj }tj |r|}ntjd|n,#t $r}tjd|Yd}~nd}~wwxYw|dkrd|}nd|}||zStd |zd |t'|zzS) NrrXz???uuG  sAA98A9)?rrxrrGrrrrHrBr contextlibrrpathlibrrtypingrpeeweer3defence360agent.application.determine_hosting_panelr defence360agent.contractsr defence360agent.contracts.configr r r rr%defence360agent.contracts.hook_eventsr&defence360agent.internals.global_scoper0defence360agent.subsys.panels.plesk.upgrade_urlsrdefence360agent.utilsrrdefence360agent.utils.commonrrdefence360agent.utils.ipechorrdefence360agent.utils.validaterrr8rIrFexistsr}rrr Exceptionrr(r!rerrKr$r%r&ris_    %%%%%%######-,,,,,<;;;;;4444448777777799999999<<<<<<<<------ " /,   t<=== EEGG/4 >?? ?K G G I I/d-.. EjjfnEEE LFjjfnEEE L DDDDD9DDDt It It It It It It It In555p  #  $       r%defence360agent/contracts/__pycache__/messages.cpython-311.opt-1.pyc0000644000000000000000000010143500000000000022232 0ustar r_jEddlZddlZddlZddlmZddlmZddlmZ Gdde Z GddZ Gd d Z Gd d ZeZGd deZGdde ZGdde ZGdde ZGddee ZGddeZGddZGddeZGddeZGdd eZGd!d"eeZGd#d$eeZGd%d&eZGd'd(eeZGd)d*eeZGd+d,eeZ Gd-d.eZ!Gd/d0eeZ"Gd1d2eZ#Gd3d4e Z$Gd5d6eZ%Gd7d8eeZ&Gd9d:eeZ'Gd;deZ)Gd?d@eZ*GdAdBeeZ+dCe,dDe-dEe,fdFZ.dGe-dHe-fdIZ/GdJdKeeZ0e-ej12dLdMZ3dEe-fdNZ4dEe-fdOZ5GdPdQZ6GdRdSeeee6Z7GdTdUeZ8GdVdWeZ9GdXdYeee6Z:GdZd[eZ;Gd\d]eeZ<Gd^d_eeZ=Gd`daeeZ>GdbdceeZ?dS)dN)Enum)List)CoreceZdZdS)MessageNotFoundErrorN__name__ __module__ __qualname__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/messages.pyrr Dr rceZdZdZdZdZdS)UnknownMessagez& Used as stub for MessageType c td)NzMessage class is not found.)rselfs r__init__zUnknownMessage.__init__s"#@AAAr cdS)NUnknownr )rnames r __getattr__zUnknownMessage.__getattr__syr N)r r r __doc__rrr r rrrs?BBBr rc8eZdZgZfdZedZxZS)MessageTc ntjdi||j|dS)Nr )super__init_subclass__ _subclassesappend)clskwargs __class__s rrzMessageT.__init_subclass__s<!!++F+++ s#####r c*t|jSN)tupler r"s rget_subclasseszMessageT.get_subclasses!sS_%%%r )r r r r r classmethodr) __classcell__r$s@rrrsXK$$$$$&&[&&&&&r rceZdZdZdZdS) _MessageTypea  Used to get specific message class. For example, >>> _MessageType().ConfigUpdate >>> _MessageType().NotExistMessage >>> cftD]}|j|kr|cStSr&)Messager)r r)rrsubclss rrz_MessageType.__getattr__0s>,,..  F$&& 'r N)r r r rrr r rr.r.&s-r r.ceZdZdZdZdS) ReportTargetapiconnN)r r r APIPERSISTENT_CONNECTIONr r rr3r3;s C"r r3c<eZdZdZejZedefdZ dS) ReportablezD Mixin class for messages that should be sent to the server methodcd|D]}|t|dkr|cSdS)ao Return a subclass with the same DEFAULT_METHOD as *method*. It can be used to detect report target from message method. NOTE: it is not guaranteed that the class with the *method* is unique, in this case the first subclass found is returned, but it is tested that all such subclasses have the same TARGET. DEFAULT_METHODN)__subclasses__getattr)r"r:subclasss rget_subclass_with_methodz#Reportable.get_subclass_with_methodGsH**,,  H+;<<<<=tr N) r r r rr3r7TARGETr*strr@r r rr9r9@sM /F c   [   r r9c.eZdZdZedefdZdS)Receivedz Mixin class for messages received from the server. These messages are created in the client360 plugin when receiving a request from imunify360.cloudlinux.com. actionc|D],}t|dgpt|dg}||vr|cS-td|)NRECEIVED_ACTIONSr<z*Message class is not found for "{}" action)r=r>rformat)r"rEr?received_actionss rget_subclass_with_actionz!Received.get_subclass_with_action^s**,,  H&x1CRHH "233M )))*" 8 ? ? G G   r N)r r r rr*rBrJr r rrDrDVsE  c    [    r rDc^eZdZdZeddZedefdZeddZdS)LockableNreturncK|jtj|_|jd{VdSr&)_lockasyncioLockacquirer(s rrRzLockable.acquirensG 9  CIi!!!!!!!!!!!r cF|jduo|jSr&)rOlockedr(s rrTzLockable.lockedts"y$;)9)9););;r cJ|j|jdSdSr&)rOreleaser(s rrVzLockable.releasexs, 9 I        ! r rMN) r r r rOr*rRboolrTrVr r rrLrLks~ E"""[" d|DS)Nc&i|]\}}|dk ||S)r:r .0kvs r z#Message.payload..s#???Ah1r itemsrs rpayloadzMessage.payloads??????r cX ||S#t$r}t||d}~wwxYw)z Called when an attribute lookup has not found the attribute in the usual places A shortcut to access an item from dict N)KeyErrorAttributeError)rrexcs rrzMessage.__getattr__s@ 0:  0 0 0 &&C / 0s )$)cfdD}djj|S)aRender for logs: collections with more than _FOLD_LIST_THRESHOLD items are collapsed to a count and strings longer than _SHORTEN_STR_THRESHOLD are shortened, recursively through nested payloads, so a single message cannot flood the log.cPi|]"\}}|t|jj#S fold_limit str_limit)_fold_repr_value_FOLD_LIST_THRESHOLD_SHORTEN_STR_THRESHOLD)rcrdrers rrfz$Message.__repr__..sP   1 45   r {}({}))rhrHr$r )r folded_msgs` r__repr__zMessage.__repr__sP         t~:JGGGr c*|Sr&)ryrs r__str__zMessage.__str__s}}r rW)r r r rr<PRIORITYPROCESSING_TIME_THRESHOLDrurvrpropertyrirryr{r+r,s@rr0r0~s NH " 777777 @@X@ 0 0 0 H H Hr r0c4eZdZfdZedZxZS) MessageListcLt|dS)Nlist)rr)rmsg_listr$s rrzMessageList.__init__s$ h'''''r c|jSr&rrs rrizMessageList.payloads yr )r r r rr~rir+r,s@rrrsS(((((Xr rceZdZdZdefdZdS)ShortenReprListMixinz Do not flood console.log with large sequences The method collapses messages that are a list. Instead of showing all the elements of the message, their number will be displayed. rc d|jjdt|dgS)Nrw <{} item(s)>rh)rHr$r lengetrs rryzShortenReprListMixin.__repr__sG N '  ! !#dhhw&;&;"<"< = =   r N)r r r rdictryr r rrrs9 t      r rc"eZdZdZeZdefdZdS) AccumulatablezMessages of this class will be grouped into a list of LIST_CLASS message instance by Accumulate plugin. Messages whose do_accumulate() call returns False will not be added to list.rMcdS)zAReturn True if this message is worth collecting, False otherwise.Tr rs r do_accumulatezAccumulatable.do_accumulatestr N)r r r rr LIST_CLASSrXrr r rrrs@55Jtr rceZdZdS)ServerConnectedNrr r rrrrr rceZdZdS)ServerReconnectedNrr r rrrrr rc*eZdZdZdZdZfdZxZS)Pingzr Will send this message on connected, reconnected events to provide central server with agent version PINGrcfttj|d<dS)Nversion)rr CoreConfigVERSION)rr$s rrz Ping.__init__s) $,Yr )r r r rr<r|rr+r,s@rrrsN NH---------r rc&eZdZdZdZfdZxZS)Ackzd Notify Server that a persistent message with *seq_number* has been received by Agent. ACKc `tjdi|t||d<dS)N)per_seq_metar )rrr)r seq_numberr#r$s rrz Ack.__init__s8""6"""Z000W r )r r r rr<rr+r,s@rrrsI N111111111r rceZdZdZdZdS)NoopzG Sending NOOP to the agent to track the message in agent logs. NOOPNr r r rr<r r rrrsNNNr rc*eZdZdZdZejZdZdS) ServerConfigz. Information about server environment SERVER_CONFIGc@d|jjSNz{}()rHr$r rs rryzServerConfig.__repr__}}T^8999r N r r r rr<r3r6rAryr r rrrs<%N  F:::::r rc eZdZdZejZdS)WpSecurityPluginStatsWP_SECURITY_PLUGIN_STATSN)r r r r<r3r6rAr r rrrs/N  FFFr rc*eZdZdZdZejZdZdS) DomainListz* Information about server domains DOMAIN_LISTc@d|jjSrrrs rryzDomainList.__repr__rr Nrr r rrrs<#N  F:::::r rceZdZdZdZdZdS) FilesUpdatedz/ To consume products of files.update() c||d<||d<dS)z\ :param files_type: files.Type :param files_index: files.LocalIndex files_type files_indexNr )rrrs rrzFilesUpdated.__init__(s (\)]r c\d|jj|d|dS)z? Do not flood console.log with large sequences z+{}({{'files_type':'{}', 'files_index':{}}})rrrrs rryzFilesUpdated.__repr__1s4=CC N '       r N)r r r rrryr r rrr#s<***     r rceZdZdZdZdS) UpdateFilesz9 Update files by getting message from the server UPDATENrr r rrr<sNNNr rceZdZdZdS) ConfigUpdate CONFIG_UPDATENr r r r<r r rrrD$NNNr rceZdZdZdS)Rejectz Kinda message filtering facility. Raised in order to stop message processing through plugins. Takes reason of reject as argument. N)r r r rr r rrrHs  Dr rceZdZdZdS)HealthHEALTHNrr r rrrRsNNNr rceZdZdZdS) CommandInvokeCOMMAND_INVOKENrr r rrrV%NNNr rceZdZdZdS) ScanFailed SCAN_FAILEDNrr r rrrZs"NNNr rceZdZdZdS) CleanupFailedCLEANUP_FAILEDNrr r rrr^rr rceZdZdZdZdS)RestoreFromBackupTaskz5 Creates a task to restore files from backup MALWARE_RESTORE_FROM_BACKUPNrr r rrrbs3NNNr rc BeZdZdZhdZededededefdZ dS) cPanelEvent PANEL_EVENT>planexcludenew_pkg imunify360_avimunify360_proactiveusernamehooktsfieldscfd|D}|dkr%d|vr!d|vr|d|dkr |d|d<||||dS)Ncvi|]5\}}|jv ||6Sr )lowerALLOWED_FIELDS)rcrdrer"s rrfz/cPanelEvent.from_hook_event..xsG   1wwyyC... GGIIq...r Modifyusernewuser old_username)rrdata timestamprg)r"rrrrrs` rfrom_hook_eventzcPanelEvent.from_hook_eventts         H  &  V##v&"333#)&>D s$      r N) r r r r<rr*rBfloatrrr r rrrjsj"NN  "% +0 :>   [   r rceZdZdZdS) IContactSent ICONTACT_SENTNrr r rrrrr rslimitrMc|dksJt||kr$|d|dzdz d|| dzdzdn|S)z1Shorten *s* string if its length exceeds *limit*.Nz...)r)rrs r _shorten_strrsf 19999 q66E>> uzA~  991eVq[1_%6%6#7999 r rrrsc.t|trt|St|trVt |kr"dt |Sfd|DSt|ttttfrZt |kr"dt |St|fd|DS|S)Nrc<i|]\}}|t|Srprt)rcrdrerrrss rrfz$_fold_repr_value..s?   1 jINNN   r c3<K|]}t|VdS)rqNr)rcrerrrss r z#_fold_repr_value..sF   Q: K K K      r ) isinstancerBrrrrHrhrr'set frozensettype)valuerrrss ``rrtrts/%.E9---% u:: " "!((U44 4          %$sI677 u:: " "!((U44 4tE{{            Lr ceZdZdZdZdS) BackupInfoz(Information about enabled backup backend BACKUP_INFONrr r rrrs22"NNNr rIMUNIFY360_MAX_MESSAGE_SIZEicddlm} ttj||S#t tf$r1tt|cYSwxYw)Nr)ServerJSONEncoderr() defence360agent.utils.jsonrrjsondumpsencode TypeError ValueErrorrepr)objrs rserialized_sizers<<<<<<'4:c'8999@@BBCCC z "'''499##%%&&&&&'s4=?A?>A?c6|dSt|trdSt|tr-tdt t |dzSt|t rdSt|t r|rR|r>t |dz| dz| d zSt tj |St|ttfrdtd |DzSt|tr.dtd |DzSt#|S) ulUpper bound on obj's JSON byte size as sent on the wire (ensure_ascii), biased to never undercount. Far cheaper than a full ``serialized_size`` per call on big scans: JSON-native values are measured structurally without building the encoded string, and printable-ASCII strings (the common path for file paths/snippets) are counted with C-level ``str`` ops. Non-native values (peewee Models, IPs, ...) fall back to the exact ``serialized_size`` — their ``repr`` would wildly undercount the ServerJSONEncoder output. The transport keeps a split-on-overflow net for the rare drift this leaves.Nrrr"\c3:K|]}t|dzVdSrN estimate_size)rcres rrz estimate_size..s/99}Q''!+999999r c3K|]P\}}tt|tr|nt|dzt|zdzVQdSr)rrrBrbs rrz estimate_size..s  1 z!S11=!!s1vv > > A        r )rrXintmaxrrBrisascii isprintablecountr r rr'sumrrhr)rs rrrs {q#tq#s*2s3s88}}q()))#ur#s$ ;;== CS__.. Cs88a<#))C..0399T??B B4:c??####e}%%:399S9999999#t 3           3  r ceZdZdZdZdZdZedefdZ ede e fdZ ede defdZedefd Zed Zede e fd ZdS) Splittablez A message list could be split into multiple batches. The split is possible for a list itself along with internal resources. NrMctSr&)MAX_MESSAGE_SIZEr(s r_max_message_sizezSplittable._max_message_sizesr messagesc#JK|jr|jr}|D]x}||jx}|V#t|}|||D]/}|}|||j<||}|V0ydSt |Ed{VdS)z Split messages' internal lists of things into batches. A field that is meant to split is defined by `BATCH_FIELD`. N) BATCH_FIELD BATCH_SIZErr_size_bounded_batchescopyiter)r"r(messagerh message_classbatchr new_messages r _split_itemszSplittable._split_itemss ? &s~ &# * *$[[999EB!MMMM$(MMM!$!:!:5'!J!J**&||~~05S_-&3mD&9&9 ))))) * * *H~~ % % % % % % % % %r is_dictcDt|r|d|din|S)zSerialized byte cost of one BATCH_FIELD unit. Subclasses override to also count data paired with the unit in sibling fields of the message (e.g. a per-hit cleanup result), so those bytes are not excluded from the byte budget.rrr)r"unitr4r/s r _unit_sizezSplittable._unit_sizes) 7Dd1gtAw//EEEr c t|S)zSubclasses override when the list class drops part of the message before sending, so the budget counts only the bytes that go out.r)r"r/s r _message_sizezSplittable._message_sizesW%%%r c# K|}t|t r!t|n|} fd}g}d}|D]g}|| |} |r2|| z|kst ||jkr||Vgd}}|||| z }h|r||VdSdS)zPack `items` into batches bounded by both the byte budget and the `BATCH_SIZE` count. A single element larger than the budget is emitted alone rather than dropped.cDrt|nt|Sr&)rr)bufferr4s rbuildz/Splittable._size_bounded_batches..build%s#*<4<<<V >I %y 6))S[[CN-J-JeFmm###!1 MM$    I DD  %--       r c#PK|jpt|}|}g}d}||D]W}||}|r$||z|kst||kr|Vgd}}||||z }X|r|VdSdS)Nr) LIST_SIZErr'r3r9r!)r"r( list_sizer>r<r@r/ message_sizes rbatchedzSplittable.batched6sM2S]] &&((''11 ! !G,,W55L %|#f,,F y0H0H !1 MM' " " " L DD  LLLLL  r )r r r rrCr+r*r*rr'rrr3rXr7r9r,rFr r rr$r$s" IJK #   [ &D$7&&&[&&FtFFFF[F&s&&&[&   [ 2tM2[r r$ceZdZdZdS) MDSReportList MDS_SCAN_LISTNrr r rrHrHIrr rHceZdZeZdS) MDSReportN)r r r rHrr r rrKrKMsJJJr rKceZdZdZdZdS)EnsureServiceStatez-Ensure the service has the appropriate statusENSURE_SERVICE_STATENrr r rrMrMQs77+NNNr rMceZdZdZdZdS)SensorWordpressIncidentListzAggregated incident list INCIDENT_LISTNrr r rrPrPWs""$NNNr rPceZdZdZdS)WordpressPluginActionWP_SECURITY_PLUGIN_ACTIONNrr r rrSrS]s0NNNr rSc*eZdZdZdZejZdZdS)WordpressPluginTelemetryzX Information about telemetry event related to Imunify Security WordPress plugin WP_SECURITY_PLUGIN_EVENTc@d|jjSrrrs rryz!WordpressPluginTelemetry.__repr__irr Nrr r rrVrVas<0N  F:::::r rVceZdZdZdZdS)WPRuleDisabledz#WordPress protection rule disabled. RULE_DISABLEDNrr r rrZrZms--$NNNr rZceZdZdZdZdS) WPRuleEnabledz%WordPress protection rule re-enabled. RULE_ENABLEDNrr r rr]r]ss//#NNNr r]ceZdZdZdS)GeneralMetricsGENERAL_METRICSNrr r rr`r`ys&NNNr r`)@rPr osenumrtypingr defence360agent.contracts.configrr Exceptionrrrr. MessageTyper3r9rDrLrr0rrrrrrrrrrrrrrrrrrrrrrrBrrrtrenvironrr&rrr$rHrKrMrPrSrVrZr]r`r r rris' ??????     9            & & & & & & & &$lnn #####4### ,     x   *     x   &44444dH444n'              G        g            - - - - -7J - - - 1 1 1 1 1': 1 1 17 : : : : :7J : : :GZ : : : : :* : : :     7   2'8%%%%%7%%%     Y   W&&&&&GZ&&&#####*###&&&&&GZ&&&33333G333" " " " " '" " " J%%%%%7J%%%C33(#####*###3JNN0+>> 'C'''' #    D[[[[[[[[|%%%%%(':z%%% ,,,,,,,, %%%%%+z:%%% 11111G111 : : : : :w : : :%%%%%Wj%%% $$$$$GZ$$$ '''''[*'''''r defence360agent/contracts/__pycache__/messages.cpython-311.pyc0000644000000000000000000010143500000000000021273 0ustar r_jEddlZddlZddlZddlmZddlmZddlmZ Gdde Z GddZ Gd d Z Gd d ZeZGd deZGdde ZGdde ZGdde ZGddee ZGddeZGddZGddeZGddeZGdd eZGd!d"eeZGd#d$eeZGd%d&eZGd'd(eeZGd)d*eeZGd+d,eeZ Gd-d.eZ!Gd/d0eeZ"Gd1d2eZ#Gd3d4e Z$Gd5d6eZ%Gd7d8eeZ&Gd9d:eeZ'Gd;deZ)Gd?d@eZ*GdAdBeeZ+dCe,dDe-dEe,fdFZ.dGe-dHe-fdIZ/GdJdKeeZ0e-ej12dLdMZ3dEe-fdNZ4dEe-fdOZ5GdPdQZ6GdRdSeeee6Z7GdTdUeZ8GdVdWeZ9GdXdYeee6Z:GdZd[eZ;Gd\d]eeZ<Gd^d_eeZ=Gd`daeeZ>GdbdceeZ?dS)dN)Enum)List)CoreceZdZdS)MessageNotFoundErrorN__name__ __module__ __qualname__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/messages.pyrr Dr rceZdZdZdZdZdS)UnknownMessagez& Used as stub for MessageType c td)NzMessage class is not found.)rselfs r__init__zUnknownMessage.__init__s"#@AAAr cdS)NUnknownr )rnames r __getattr__zUnknownMessage.__getattr__syr N)r r r __doc__rrr r rrrs?BBBr rc8eZdZgZfdZedZxZS)MessageTc ntjdi||j|dS)Nr )super__init_subclass__ _subclassesappend)clskwargs __class__s rrzMessageT.__init_subclass__s<!!++F+++ s#####r c*t|jSN)tupler r"s rget_subclasseszMessageT.get_subclasses!sS_%%%r )r r r r r classmethodr) __classcell__r$s@rrrsXK$$$$$&&[&&&&&r rceZdZdZdZdS) _MessageTypea  Used to get specific message class. For example, >>> _MessageType().ConfigUpdate >>> _MessageType().NotExistMessage >>> cftD]}|j|kr|cStSr&)Messager)r r)rrsubclss rrz_MessageType.__getattr__0s>,,..  F$&& 'r N)r r r rrr r rr.r.&s-r r.ceZdZdZdZdS) ReportTargetapiconnN)r r r APIPERSISTENT_CONNECTIONr r rr3r3;s C"r r3c<eZdZdZejZedefdZ dS) ReportablezD Mixin class for messages that should be sent to the server methodcd|D]}|t|dkr|cSdS)ao Return a subclass with the same DEFAULT_METHOD as *method*. It can be used to detect report target from message method. NOTE: it is not guaranteed that the class with the *method* is unique, in this case the first subclass found is returned, but it is tested that all such subclasses have the same TARGET. DEFAULT_METHODN)__subclasses__getattr)r"r:subclasss rget_subclass_with_methodz#Reportable.get_subclass_with_methodGsH**,,  H+;<<<<=tr N) r r r rr3r7TARGETr*strr@r r rr9r9@sM /F c   [   r r9c.eZdZdZedefdZdS)Receivedz Mixin class for messages received from the server. These messages are created in the client360 plugin when receiving a request from imunify360.cloudlinux.com. actionc|D],}t|dgpt|dg}||vr|cS-td|)NRECEIVED_ACTIONSr<z*Message class is not found for "{}" action)r=r>rformat)r"rEr?received_actionss rget_subclass_with_actionz!Received.get_subclass_with_action^s**,,  H&x1CRHH "233M )))*" 8 ? ? G G   r N)r r r rr*rBrJr r rrDrDVsE  c    [    r rDc^eZdZdZeddZedefdZeddZdS)LockableNreturncK|jtj|_|jd{VdSr&)_lockasyncioLockacquirer(s rrRzLockable.acquirensG 9  CIi!!!!!!!!!!!r cF|jduo|jSr&)rOlockedr(s rrTzLockable.lockedts"y$;)9)9););;r cJ|j|jdSdSr&)rOreleaser(s rrVzLockable.releasexs, 9 I        ! r rMN) r r r rOr*rRboolrTrVr r rrLrLks~ E"""[" d|DS)Nc&i|]\}}|dk ||S)r:r .0kvs r z#Message.payload..s#???Ah1r itemsrs rpayloadzMessage.payloads??????r cX ||S#t$r}t||d}~wwxYw)z Called when an attribute lookup has not found the attribute in the usual places A shortcut to access an item from dict N)KeyErrorAttributeError)rrexcs rrzMessage.__getattr__s@ 0:  0 0 0 &&C / 0s )$)cfdD}djj|S)aRender for logs: collections with more than _FOLD_LIST_THRESHOLD items are collapsed to a count and strings longer than _SHORTEN_STR_THRESHOLD are shortened, recursively through nested payloads, so a single message cannot flood the log.cPi|]"\}}|t|jj#S fold_limit str_limit)_fold_repr_value_FOLD_LIST_THRESHOLD_SHORTEN_STR_THRESHOLD)rcrdrers rrfz$Message.__repr__..sP   1 45   r {}({}))rhrHr$r )r folded_msgs` r__repr__zMessage.__repr__sP         t~:JGGGr c*|Sr&)ryrs r__str__zMessage.__str__s}}r rW)r r r rr<PRIORITYPROCESSING_TIME_THRESHOLDrurvrpropertyrirryr{r+r,s@rr0r0~s NH " 777777 @@X@ 0 0 0 H H Hr r0c4eZdZfdZedZxZS) MessageListcLt|dS)Nlist)rr)rmsg_listr$s rrzMessageList.__init__s$ h'''''r c|jSr&rrs rrizMessageList.payloads yr )r r r rr~rir+r,s@rrrsS(((((Xr rceZdZdZdefdZdS)ShortenReprListMixinz Do not flood console.log with large sequences The method collapses messages that are a list. Instead of showing all the elements of the message, their number will be displayed. rc d|jjdt|dgS)Nrw <{} item(s)>rh)rHr$r lengetrs rryzShortenReprListMixin.__repr__sG N '  ! !#dhhw&;&;"<"< = =   r N)r r r rdictryr r rrrs9 t      r rc"eZdZdZeZdefdZdS) AccumulatablezMessages of this class will be grouped into a list of LIST_CLASS message instance by Accumulate plugin. Messages whose do_accumulate() call returns False will not be added to list.rMcdS)zAReturn True if this message is worth collecting, False otherwise.Tr rs r do_accumulatezAccumulatable.do_accumulatestr N)r r r rr LIST_CLASSrXrr r rrrs@55Jtr rceZdZdS)ServerConnectedNrr r rrrrr rceZdZdS)ServerReconnectedNrr r rrrrr rc*eZdZdZdZdZfdZxZS)Pingzr Will send this message on connected, reconnected events to provide central server with agent version PINGrcfttj|d<dS)Nversion)rr CoreConfigVERSION)rr$s rrz Ping.__init__s) $,Yr )r r r rr<r|rr+r,s@rrrsN NH---------r rc&eZdZdZdZfdZxZS)Ackzd Notify Server that a persistent message with *seq_number* has been received by Agent. ACKc `tjdi|t||d<dS)N)per_seq_metar )rrr)r seq_numberr#r$s rrz Ack.__init__s8""6"""Z000W r )r r r rr<rr+r,s@rrrsI N111111111r rceZdZdZdZdS)NoopzG Sending NOOP to the agent to track the message in agent logs. NOOPNr r r rr<r r rrrsNNNr rc*eZdZdZdZejZdZdS) ServerConfigz. Information about server environment SERVER_CONFIGc@d|jjSNz{}()rHr$r rs rryzServerConfig.__repr__}}T^8999r N r r r rr<r3r6rAryr r rrrs<%N  F:::::r rc eZdZdZejZdS)WpSecurityPluginStatsWP_SECURITY_PLUGIN_STATSN)r r r r<r3r6rAr r rrrs/N  FFFr rc*eZdZdZdZejZdZdS) DomainListz* Information about server domains DOMAIN_LISTc@d|jjSrrrs rryzDomainList.__repr__rr Nrr r rrrs<#N  F:::::r rceZdZdZdZdZdS) FilesUpdatedz/ To consume products of files.update() c||d<||d<dS)z\ :param files_type: files.Type :param files_index: files.LocalIndex files_type files_indexNr )rrrs rrzFilesUpdated.__init__(s (\)]r c\d|jj|d|dS)z? Do not flood console.log with large sequences z+{}({{'files_type':'{}', 'files_index':{}}})rrrrs rryzFilesUpdated.__repr__1s4=CC N '       r N)r r r rrryr r rrr#s<***     r rceZdZdZdZdS) UpdateFilesz9 Update files by getting message from the server UPDATENrr r rrr<sNNNr rceZdZdZdS) ConfigUpdate CONFIG_UPDATENr r r r<r r rrrD$NNNr rceZdZdZdS)Rejectz Kinda message filtering facility. Raised in order to stop message processing through plugins. Takes reason of reject as argument. N)r r r rr r rrrHs  Dr rceZdZdZdS)HealthHEALTHNrr r rrrRsNNNr rceZdZdZdS) CommandInvokeCOMMAND_INVOKENrr r rrrV%NNNr rceZdZdZdS) ScanFailed SCAN_FAILEDNrr r rrrZs"NNNr rceZdZdZdS) CleanupFailedCLEANUP_FAILEDNrr r rrr^rr rceZdZdZdZdS)RestoreFromBackupTaskz5 Creates a task to restore files from backup MALWARE_RESTORE_FROM_BACKUPNrr r rrrbs3NNNr rc BeZdZdZhdZededededefdZ dS) cPanelEvent PANEL_EVENT>planexcludenew_pkg imunify360_avimunify360_proactiveusernamehooktsfieldscfd|D}|dkr%d|vr!d|vr|d|dkr |d|d<||||dS)Ncvi|]5\}}|jv ||6Sr )lowerALLOWED_FIELDS)rcrdrer"s rrfz/cPanelEvent.from_hook_event..xsG   1wwyyC... GGIIq...r Modifyusernewuser old_username)rrdata timestamprg)r"rrrrrs` rfrom_hook_eventzcPanelEvent.from_hook_eventts         H  &  V##v&"333#)&>D s$      r N) r r r r<rr*rBfloatrrr r rrrjsj"NN  "% +0 :>   [   r rceZdZdZdS) IContactSent ICONTACT_SENTNrr r rrrrr rslimitrMc|dksJt||kr$|d|dzdz d|| dzdzdn|S)z1Shorten *s* string if its length exceeds *limit*.Nz...)r)rrs r _shorten_strrsf 19999 q66E>> uzA~  991eVq[1_%6%6#7999 r rrrsc.t|trt|St|trVt |kr"dt |Sfd|DSt|ttttfrZt |kr"dt |St|fd|DS|S)Nrc<i|]\}}|t|Srprt)rcrdrerrrss rrfz$_fold_repr_value..s?   1 jINNN   r c3<K|]}t|VdS)rqNr)rcrerrrss r z#_fold_repr_value..sF   Q: K K K      r ) isinstancerBrrrrHrhrr'set frozensettype)valuerrrss ``rrtrts/%.E9---% u:: " "!((U44 4          %$sI677 u:: " "!((U44 4tE{{            Lr ceZdZdZdZdS) BackupInfoz(Information about enabled backup backend BACKUP_INFONrr r rrrs22"NNNr rIMUNIFY360_MAX_MESSAGE_SIZEicddlm} ttj||S#t tf$r1tt|cYSwxYw)Nr)ServerJSONEncoderr() defence360agent.utils.jsonrrjsondumpsencode TypeError ValueErrorrepr)objrs rserialized_sizers<<<<<<'4:c'8999@@BBCCC z "'''499##%%&&&&&'s4=?A?>A?c6|dSt|trdSt|tr-tdt t |dzSt|t rdSt|t r|rR|r>t |dz| dz| d zSt tj |St|ttfrdtd |DzSt|tr.dtd |DzSt#|S) ulUpper bound on obj's JSON byte size as sent on the wire (ensure_ascii), biased to never undercount. Far cheaper than a full ``serialized_size`` per call on big scans: JSON-native values are measured structurally without building the encoded string, and printable-ASCII strings (the common path for file paths/snippets) are counted with C-level ``str`` ops. Non-native values (peewee Models, IPs, ...) fall back to the exact ``serialized_size`` — their ``repr`` would wildly undercount the ServerJSONEncoder output. The transport keeps a split-on-overflow net for the rare drift this leaves.Nrrr"\c3:K|]}t|dzVdSrN estimate_size)rcres rrz estimate_size..s/99}Q''!+999999r c3K|]P\}}tt|tr|nt|dzt|zdzVQdSr)rrrBrbs rrz estimate_size..s  1 z!S11=!!s1vv > > A        r )rrXintmaxrrBrisascii isprintablecountr r rr'sumrrhr)rs rrrs {q#tq#s*2s3s88}}q()))#ur#s$ ;;== CS__.. Cs88a<#))C..0399T??B B4:c??####e}%%:399S9999999#t 3           3  r ceZdZdZdZdZdZedefdZ ede e fdZ ede defdZedefd Zed Zede e fd ZdS) Splittablez A message list could be split into multiple batches. The split is possible for a list itself along with internal resources. NrMctSr&)MAX_MESSAGE_SIZEr(s r_max_message_sizezSplittable._max_message_sizesr messagesc#JK|jr|jr}|D]x}||jx}|V#t|}|||D]/}|}|||j<||}|V0ydSt |Ed{VdS)z Split messages' internal lists of things into batches. A field that is meant to split is defined by `BATCH_FIELD`. N) BATCH_FIELD BATCH_SIZErr_size_bounded_batchescopyiter)r"r(messagerh message_classbatchr new_messages r _split_itemszSplittable._split_itemss ? &s~ &# * *$[[999EB!MMMM$(MMM!$!:!:5'!J!J**&||~~05S_-&3mD&9&9 ))))) * * *H~~ % % % % % % % % %r is_dictcDt|r|d|din|S)zSerialized byte cost of one BATCH_FIELD unit. Subclasses override to also count data paired with the unit in sibling fields of the message (e.g. a per-hit cleanup result), so those bytes are not excluded from the byte budget.rrr)r"unitr4r/s r _unit_sizezSplittable._unit_sizes) 7Dd1gtAw//EEEr c t|S)zSubclasses override when the list class drops part of the message before sending, so the budget counts only the bytes that go out.r)r"r/s r _message_sizezSplittable._message_sizesW%%%r c# K|}t|t r!t|n|} fd}g}d}|D]g}|| |} |r2|| z|kst ||jkr||Vgd}}|||| z }h|r||VdSdS)zPack `items` into batches bounded by both the byte budget and the `BATCH_SIZE` count. A single element larger than the budget is emitted alone rather than dropped.cDrt|nt|Sr&)rr)bufferr4s rbuildz/Splittable._size_bounded_batches..build%s#*<4<<<V >I %y 6))S[[CN-J-JeFmm###!1 MM$    I DD  %--       r c#PK|jpt|}|}g}d}||D]W}||}|r$||z|kst||kr|Vgd}}||||z }X|r|VdSdS)Nr) LIST_SIZErr'r3r9r!)r"r( list_sizer>r<r@r/ message_sizes rbatchedzSplittable.batched6sM2S]] &&((''11 ! !G,,W55L %|#f,,F y0H0H !1 MM' " " " L DD  LLLLL  r )r r r rrCr+r*r*rr'rrr3rXr7r9r,rFr r rr$r$s" IJK #   [ &D$7&&&[&&FtFFFF[F&s&&&[&   [ 2tM2[r r$ceZdZdZdS) MDSReportList MDS_SCAN_LISTNrr r rrHrHIrr rHceZdZeZdS) MDSReportN)r r r rHrr r rrKrKMsJJJr rKceZdZdZdZdS)EnsureServiceStatez-Ensure the service has the appropriate statusENSURE_SERVICE_STATENrr r rrMrMQs77+NNNr rMceZdZdZdZdS)SensorWordpressIncidentListzAggregated incident list INCIDENT_LISTNrr r rrPrPWs""$NNNr rPceZdZdZdS)WordpressPluginActionWP_SECURITY_PLUGIN_ACTIONNrr r rrSrS]s0NNNr rSc*eZdZdZdZejZdZdS)WordpressPluginTelemetryzX Information about telemetry event related to Imunify Security WordPress plugin WP_SECURITY_PLUGIN_EVENTc@d|jjSrrrs rryz!WordpressPluginTelemetry.__repr__irr Nrr r rrVrVas<0N  F:::::r rVceZdZdZdZdS)WPRuleDisabledz#WordPress protection rule disabled. RULE_DISABLEDNrr r rrZrZms--$NNNr rZceZdZdZdZdS) WPRuleEnabledz%WordPress protection rule re-enabled. RULE_ENABLEDNrr r rr]r]ss//#NNNr r]ceZdZdZdS)GeneralMetricsGENERAL_METRICSNrr r rr`r`ys&NNNr r`)@rPr osenumrtypingr defence360agent.contracts.configrr Exceptionrrrr. MessageTyper3r9rDrLrr0rrrrrrrrrrrrrrrrrrrrrrrBrrrtrenvironrr&rrr$rHrKrMrPrSrVrZr]r`r r rris' ??????     9            & & & & & & & &$lnn #####4### ,     x   *     x   &44444dH444n'              G        g            - - - - -7J - - - 1 1 1 1 1': 1 1 17 : : : : :7J : : :GZ : : : : :* : : :     7   2'8%%%%%7%%%     Y   W&&&&&GZ&&&#####*###&&&&&GZ&&&33333G333" " " " " '" " " J%%%%%7J%%%C33(#####*###3JNN0+>> 'C'''' #    D[[[[[[[[|%%%%%(':z%%% ,,,,,,,, %%%%%+z:%%% 11111G111 : : : : :w : : :%%%%%Wj%%% $$$$$GZ$$$ '''''[*'''''r defence360agent/contracts/__pycache__/myimunify_id.cpython-311.opt-1.pyc0000644000000000000000000002362500000000000023131 0ustar r_j lddlZddlZddlZddlZddlmZddlmZmZm Z ddl m Z ddl m Z ddlmZmZddlmZddlmZd Zd Zd Zed ZGd deZdedede efdZdeefdZdeeeffdZ dedefdZ!dededefdZ"dedefdZ#dedefdZ$dedefdZ%dS)N)Path)DictListOptional)logger)instance) MyImunifyupdate_users_protection) HostingPanel) safe_fileopsz .myimunify_idzE# DO NOT EDIT # This file contains MyImunify id unique to this user 0123456789abcdefceZdZdZdS)MyImunifyIdErrorz5Exception representing issues related to MyImunify idN)__name__ __module__ __qualname____doc__[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/myimunify_id.pyrrs????rruser protectionreturncKtj|\}}|t||g|d{Vt jd|| t |d{V}n#t$rYdSwxYw|S)z5Save subscription type to the DB and generate id filerNz(Applied setting MyImunify=%s for user %s)r get_or_createsaver rinfo_get_or_generate_idr)sinkrr myimunify_ myimunify_ids radd_myimunify_userr%s *555LIq NN !$ ; ;;;;;;;; K:JMMM066666666 tt sA44 BBc NKg}td{V}td{V}tj5t |D]\}}tj |\}}| | |i dd|||j | |i ddd dddn #1swxYwY|S)zP Get a list of MyImunify users, their subscription types and unique ids Nremaillocale)r'usernamer$rr)) r get_user_details_myimunify_user_to_idrdb transactionsorteditemsr rappendgetr)users user_detailsmyimunify_user_to_idr myimunify_uidrecordr#s rget_myimunify_usersr8.s~ E%88::::::::L!6!8!8888888  " "  #)*>*D*D*F*F#G#G   D-!/T:::IFA LL)--dB77;;GRHH $$1"("3*..tR88<%>>>>>>>Jt      H/    N=tSVV    HHHH   sA BB!#B  BcKt|d{V} t|S#ttf$r1t jj}t||d{VcYSwxYw)z Read MyImunify id if exists and valid, or generate a new one and write into the file. Malformed files are regenerated. N)_get_myimunify_id_file_read_idFileNotFoundErrorruuiduuid1hex _write_id)rid_filer$s rr r Ws +400000000G6   / 0666z||' |W555555555556s(?A*)A*r$rHcKt|zdz} tjt||d{Vn/#t$r"}t jd|t|d}~wwxYw|S)zWrite MyImunify id to file Nz1Unable to write myimunify_id in user home dir: %s)_BANNERr write_textr=OSErrorrr<r)r$rHtextr?s rrGrGds \ !D (D&%c'llD9999999999 &&&JANNNA%& s(: A&A!!A&c tjt|tjtjz}n#t $rt $rtwxYw tj tj |j sttj |d}| d}n#t$rtwxYw tj|n#tj|wxYwt!|S)anRead and validate MyImunify id from file. Raises MyImunifyIdError if malformed. Opens with O_RDONLY | O_NONBLOCK and verifies via fstat() that the fd refers to a regular file before reading. This eliminates the TOCTOU window between a path-level type check and the actual read (e.g. an attacker replacing the file with a FIFO between the two). i zutf-8)osopenr=O_RDONLY O_NONBLOCKrCrMrstatS_ISREGfstatst_modereaddecodeUnicodeDecodeErrorclose _parse_id)rHfddatarNs rrBrBos WS\\2;#> ? ?  |BHRLL011 #" "wr4  {{7##     T??s(9<AAB98C$9C  C$$C:rNc*d}|D]r}|}|s|dr/|tt |t kst d|Dst|}s|t|S)z`Read line by line: skip comments (#). First non-comment line must be valid id; nothing after it.N#c3(K|] }|tvVdS)N)_HEX).0cs r z_parse_id..s&'='=aT '='='='='='=r) splitlinesstrip startswithrlen_ID_LENall)rNid_lineliness rr\r\sG!!   JJLL   <<      " " q66W  C'='=1'='='=$=$= " " NrcK tj|}t|jtz } t jt|n#t$r|j stj d|t t jt|d{Vn/#t$r"}tj d|t|d}~wwxYwYnXt$rtj d|twxYw#t $r"}tj d|t|d}~wwxYw|S)z((** '94@@@&& ."(W6666666666 . . .Eq'A-  .76  # # # ND? C+ C&&C++D0&D E#EE)&rPrprTrDpathlibrtypingrrr%defence360agent.contracts.permissionsrdefence360agent.modelrdefence360agent.myimunify.modelr r +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr rsrKrj frozensetrb Exceptionrr=boolr%r8r,r rGrBr\rArrrrs@ ''''''''''888888******NNNNNNNNDDDDDD......(O y#$$@@@@@y@@@!% c]&4:.T#s(^$ 6C 6C 6 6 6 6#ds4CC&strdefence360agent/contracts/__pycache__/myimunify_id.cpython-311.pyc0000644000000000000000000002362500000000000022172 0ustar r_j lddlZddlZddlZddlZddlmZddlmZmZm Z ddl m Z ddl m Z ddlmZmZddlmZddlmZd Zd Zd Zed ZGd deZdedede efdZdeefdZdeeeffdZ dedefdZ!dededefdZ"dedefdZ#dedefdZ$dedefdZ%dS)N)Path)DictListOptional)logger)instance) MyImunifyupdate_users_protection) HostingPanel) safe_fileopsz .myimunify_idzE# DO NOT EDIT # This file contains MyImunify id unique to this user 0123456789abcdefceZdZdZdS)MyImunifyIdErrorz5Exception representing issues related to MyImunify idN)__name__ __module__ __qualname____doc__[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/myimunify_id.pyrrs????rruser protectionreturncKtj|\}}|t||g|d{Vt jd|| t |d{V}n#t$rYdSwxYw|S)z5Save subscription type to the DB and generate id filerNz(Applied setting MyImunify=%s for user %s)r get_or_createsaver rinfo_get_or_generate_idr)sinkrr myimunify_ myimunify_ids radd_myimunify_userr%s *555LIq NN !$ ; ;;;;;;;; K:JMMM066666666 tt sA44 BBc NKg}td{V}td{V}tj5t |D]\}}tj |\}}| | |i dd|||j | |i ddd dddn #1swxYwY|S)zP Get a list of MyImunify users, their subscription types and unique ids Nremaillocale)r'usernamer$rr)) r get_user_details_myimunify_user_to_idrdb transactionsorteditemsr rappendgetr)users user_detailsmyimunify_user_to_idr myimunify_uidrecordr#s rget_myimunify_usersr8.s~ E%88::::::::L!6!8!8888888  " "  #)*>*D*D*F*F#G#G   D-!/T:::IFA LL)--dB77;;GRHH $$1"("3*..tR88<%>>>>>>>Jt      H/    N=tSVV    HHHH   sA BB!#B  BcKt|d{V} t|S#ttf$r1t jj}t||d{VcYSwxYw)z Read MyImunify id if exists and valid, or generate a new one and write into the file. Malformed files are regenerated. N)_get_myimunify_id_file_read_idFileNotFoundErrorruuiduuid1hex _write_id)rid_filer$s rr r Ws +400000000G6   / 0666z||' |W555555555556s(?A*)A*r$rHcKt|zdz} tjt||d{Vn/#t$r"}t jd|t|d}~wwxYw|S)zWrite MyImunify id to file Nz1Unable to write myimunify_id in user home dir: %s)_BANNERr write_textr=OSErrorrr<r)r$rHtextr?s rrGrGds \ !D (D&%c'llD9999999999 &&&JANNNA%& s(: A&A!!A&c tjt|tjtjz}n#t $rt $rtwxYw tj tj |j sttj |d}| d}n#t$rtwxYw tj|n#tj|wxYwt!|S)anRead and validate MyImunify id from file. Raises MyImunifyIdError if malformed. Opens with O_RDONLY | O_NONBLOCK and verifies via fstat() that the fd refers to a regular file before reading. This eliminates the TOCTOU window between a path-level type check and the actual read (e.g. an attacker replacing the file with a FIFO between the two). i zutf-8)osopenr=O_RDONLY O_NONBLOCKrCrMrstatS_ISREGfstatst_modereaddecodeUnicodeDecodeErrorclose _parse_id)rHfddatarNs rrBrBos WS\\2;#> ? ?  |BHRLL011 #" "wr4  {{7##     T??s(9<AAB98C$9C  C$$C:rNc*d}|D]r}|}|s|dr/|tt |t kst d|Dst|}s|t|S)z`Read line by line: skip comments (#). First non-comment line must be valid id; nothing after it.N#c3(K|] }|tvVdS)N)_HEX).0cs r z_parse_id..s&'='=aT '='='='='='=r) splitlinesstrip startswithrlen_ID_LENall)rNid_lineliness rr\r\sG!!   JJLL   <<      " " q66W  C'='=1'='='=$=$= " " NrcK tj|}t|jtz } t jt|n#t$r|j stj d|t t jt|d{Vn/#t$r"}tj d|t|d}~wwxYwYnXt$rtj d|twxYw#t $r"}tj d|t|d}~wwxYw|S)z((** '94@@@&& ."(W6666666666 . . .Eq'A-  .76  # # # ND? C+ C&&C++D0&D E#EE)&rPrprTrDpathlibrtypingrrr%defence360agent.contracts.permissionsrdefence360agent.modelrdefence360agent.myimunify.modelr r +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr rsrKrj frozensetrb Exceptionrr=boolr%r8r,r rGrBr\rArrrrs@ ''''''''''888888******NNNNNNNNDDDDDD......(O y#$$@@@@@y@@@!% c]&4:.T#s(^$ 6C 6C 6 6 6 6#ds4CC&strdefence360agent/contracts/__pycache__/permissions.cpython-311.opt-1.pyc0000644000000000000000000002307400000000000023000 0ustar r_jJddlZddlmZddlmZddlmZddlmZm Z m Z ddl m Z ddl mZmZddlmZdd lmZdd lmZdd lmZdd lmZ dd lmZn #e$rdZYnwxYwejeZdxZ \ Z!Z"Z#Z$Z%Z&Z'Z(Z)Z*Z+Z,Z-edZ.de/fdZ0d*dee1de/fdZ2d*dee1de/fdZ3d*dee1de/fdZ4d*dee1fdZ5d*dee1de/fdZ6 d*dee1de/fdZ7d*dee1fdZ8d*dee1fdZ9ej:dddZ;ej:dd d!Z< d*de1dzde/fd"Z=d*dee1fd#Z>d*dee1fd$Z?d*dee1fd%Z@d*dee1fd&ZAe!e3e"e4e#e5e$e6e%e7e&e8e'e9e(e;e)e=e*e>e+e?e,e@e-eAi ZBde/fd'ZCd+d(ZDdeEe1fd)ZFdS),N)iscoroutinefunction)Path)Optional)MyImunifyConfigPermissionsConfig Wordpress) LicenseCLN) AV_REPORTFULL)FeatureManagementPerms) MyImunify) HostingPanel)Plesk)importer)ImunifyPatchSubscriptionAPI) zmalware_scanner.viewzmalware_scanner.cleanz3malware_scanner.clean_requires_myimunify_protectionzmalware_scanner.on_demand.scanz1malware_scanner.on_demand.scan_without_rate_limitz malware_scanner.ignore_list.editz*malware_scanner.config.default_action.editz%malware_scanner.imunify_patch.enabledz2malware_scanner.imunify_patch.eligible_to_purchasezproactive_defense.viewz"proactive_defense.config.mode.editzwordpress.waf.editzwordpress.waf.rules.editz/etc/sysconfig/imunify360returnc\tjtjko tjSN)rNAMErrUSE_PLESK_SERVICE_PLANZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/permissions.pyis_plesk_service_plan_enabledr=s#uz) 5  4ruserc*tj|Sr)r get_protectionrs rmyimunify_protection_enabledrDs  #D ) ))rcX|dStj|jttfvSNT)r get_permavr r rs rms_viewr$Hs2 |t ! *4 0 0 3 8 rctjstjsdS|dStrdSt j|jtkS)NFT)r is_freeis_validrr r"r#r rs rms_cleanr(Rs^:#6#8#8u |t$&&t ! *4 0 0 3t ;;rcVtjrt|St|Sr)rENABLEDrr(rs r&ms_clean_requires_myimunify_protectionr+`s'2+D111 D>>rc^|dStjrdStrdStjSr!)rr*rrALLOW_MALWARE_SCANrs rms_on_demand_scanr.fs8 |tt$&&t  //rcPtjrt|StjSr)rr*rrr-rs r$ms_on_demand_scan_without_rate_limitr0us&2+D111  //rc>|dStjrdStjSNTF)rr*rUSER_IGNORE_LISTrs rms_ignore_list_editr4~s% |tu  --rc>|dStjrdStjSr2)rr*rUSER_OVERRIDE_MALWARE_ACTIONSrs rms_config_default_action_editr7s' |tu  ::rzimav.contracts.permissionsis_imunify_patch_enabledcdSNFr_s rr=er)modulenamedefaultz.imav.malwarelib.api.imunify_patch_subscriptionhas_imunify_patch_subscriptionscdSr:rr;s rr=r=r>rcKt"tjpt|Stjp,t|ptjd{VjSr)rr is_eligible_for_imunify_patchrBget_purchase_eligibilityeligiblers r%ms_imunify_patch_eligible_to_purchaserHsz#*  4 6 6 5.t44 022  *4 0 0 .FHH H H H H H H  rcN|dStj|jtkSr!)r r" proactiver rs rpd_viewrKs% |t ! *4 0 0 :d BBrc>|dStjrdStjSr2)rr*rUSER_OVERRIDE_PROACTIVE_DEFENSErs rpd_config_mode_editrNs% |tu  <z$permissions_list..sO     D11 1 1 1 1 1 1    r) PERMISSIONSrs`rpermissions_listrgsP    %         rr)rN)Gloggingasyncio.coroutinesrpathlibrtypingr defence360agent.contracts.configrrr!defence360agent.contracts.licenser ,defence360agent.feature_management.constantsr r (defence360agent.feature_management.modelr defence360agent.myimunify.modelr +defence360agent.subsys.panels.hosting_panelr#defence360agent.subsys.panels.pleskrdefence360agent.utilsr.imav.malwarelib.api.imunify_patch_subscriptionr ImportError getLogger__name__loggerrfMS_VIEWMS_CLEAN&MS_CLEAN_REQUIRES_MYIMUNIFY_PROTECTIONMS_ON_DEMAND_SCAN$MS_ON_DEMAND_SCAN_WITHOUT_RATE_LIMITMS_IGNORE_LIST_EDITMS_CONFIG_DEFAULT_ACTION_EDITMS_IMUNIFY_PATCH_ENABLED%MS_IMUNIFY_PATCH_ELIGIBLE_TO_PURCHASEPD_VIEWPD_CONFIG_MODE_EDIT WP_WAF_EDITWP_WAF_RULES_EDITGLOBAL_CONFDIRrQrstrrr$r(r+r.r0r4r7rZms_imunify_patch_enabledrBrHrKrNrTrWrYr^ralistrgrrrrs222222 988888HHHHHHHHKKKKKK555555DDDDDD555555******''''"&'  8 $ $    *(!) "122t**x}*****(3-4 < <8C= d*dee1fd$Z?d*dee1fd%Z@d*dee1fd&ZAe!e3e"e4e#e5e$e6e%e7e&e8e'e9e(e;e)e=e*e>e+e?e,e@e-eAi ZBde/fd'ZCd+d(ZDdeEe1fd)ZFdS),N)iscoroutinefunction)Path)Optional)MyImunifyConfigPermissionsConfig Wordpress) LicenseCLN) AV_REPORTFULL)FeatureManagementPerms) MyImunify) HostingPanel)Plesk)importer)ImunifyPatchSubscriptionAPI) zmalware_scanner.viewzmalware_scanner.cleanz3malware_scanner.clean_requires_myimunify_protectionzmalware_scanner.on_demand.scanz1malware_scanner.on_demand.scan_without_rate_limitz malware_scanner.ignore_list.editz*malware_scanner.config.default_action.editz%malware_scanner.imunify_patch.enabledz2malware_scanner.imunify_patch.eligible_to_purchasezproactive_defense.viewz"proactive_defense.config.mode.editzwordpress.waf.editzwordpress.waf.rules.editz/etc/sysconfig/imunify360returnc\tjtjko tjSN)rNAMErrUSE_PLESK_SERVICE_PLANZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/permissions.pyis_plesk_service_plan_enabledr=s#uz) 5  4ruserc*tj|Sr)r get_protectionrs rmyimunify_protection_enabledrDs  #D ) ))rcX|dStj|jttfvSNT)r get_permavr r rs rms_viewr$Hs2 |t ! *4 0 0 3 8 rctjstjsdS|dStrdSt j|jtkS)NFT)r is_freeis_validrr r"r#r rs rms_cleanr(Rs^:#6#8#8u |t$&&t ! *4 0 0 3t ;;rcVtjrt|St|Sr)rENABLEDrr(rs r&ms_clean_requires_myimunify_protectionr+`s'2+D111 D>>rc^|dStjrdStrdStjSr!)rr*rrALLOW_MALWARE_SCANrs rms_on_demand_scanr.fs8 |tt$&&t  //rcPtjrt|StjSr)rr*rrr-rs r$ms_on_demand_scan_without_rate_limitr0us&2+D111  //rc>|dStjrdStjSNTF)rr*rUSER_IGNORE_LISTrs rms_ignore_list_editr4~s% |tu  --rc>|dStjrdStjSr2)rr*rUSER_OVERRIDE_MALWARE_ACTIONSrs rms_config_default_action_editr7s' |tu  ::rzimav.contracts.permissionsis_imunify_patch_enabledcdSNFr_s rr=er)modulenamedefaultz.imav.malwarelib.api.imunify_patch_subscriptionhas_imunify_patch_subscriptionscdSr:rr;s rr=r=r>rcKt"tjpt|Stjp,t|ptjd{VjSr)rr is_eligible_for_imunify_patchrBget_purchase_eligibilityeligiblers r%ms_imunify_patch_eligible_to_purchaserHsz#*  4 6 6 5.t44 022  *4 0 0 .FHH H H H H H H  rcN|dStj|jtkSr!)r r" proactiver rs rpd_viewrKs% |t ! *4 0 0 :d BBrc>|dStjrdStjSr2)rr*rUSER_OVERRIDE_PROACTIVE_DEFENSErs rpd_config_mode_editrNs% |tu  <z$permissions_list..sO     D11 1 1 1 1 1 1    r) PERMISSIONSrs`rpermissions_listrgsP    %         rr)rN)Gloggingasyncio.coroutinesrpathlibrtypingr defence360agent.contracts.configrrr!defence360agent.contracts.licenser ,defence360agent.feature_management.constantsr r (defence360agent.feature_management.modelr defence360agent.myimunify.modelr +defence360agent.subsys.panels.hosting_panelr#defence360agent.subsys.panels.pleskrdefence360agent.utilsr.imav.malwarelib.api.imunify_patch_subscriptionr ImportError getLogger__name__loggerrfMS_VIEWMS_CLEAN&MS_CLEAN_REQUIRES_MYIMUNIFY_PROTECTIONMS_ON_DEMAND_SCAN$MS_ON_DEMAND_SCAN_WITHOUT_RATE_LIMITMS_IGNORE_LIST_EDITMS_CONFIG_DEFAULT_ACTION_EDITMS_IMUNIFY_PATCH_ENABLED%MS_IMUNIFY_PATCH_ELIGIBLE_TO_PURCHASEPD_VIEWPD_CONFIG_MODE_EDIT WP_WAF_EDITWP_WAF_RULES_EDITGLOBAL_CONFDIRrQrstrrr$r(r+r.r0r4r7rZms_imunify_patch_enabledrBrHrKrNrTrWrYr^ralistrgrrrrs222222 988888HHHHHHHHKKKKKK555555DDDDDD555555******''''"&'  8 $ $    *(!) "122t**x}*****(3-4 < <8C= z1BasePlugin.get_active_plugins..s9   %f--    r)r)rs rget_active_pluginszBasePlugin.get_active_pluginss%  /    rc KdS)aZShutdown plugin's subsystems, cancel running tasks, clean iptables (if plugin is protector). It should be safe to assume that it is called after corresponding create_source if applicable. It is called only from the shutdown task that runs at most once, meaning shutdown() is never called twice. Nrselfs rshutdownzBasePlugin.shutdown"s  rc8|jjd|jjS)N.)r __module____name__r#s r__repr__zBasePlugin.__repr__/s .333T^5L5LMMr)r)r( __qualname__r AV_IM360SCOPESHUTDOWN_PRIORITYAVAILABLE_ON_FREEMIUMrr classmethodr!r%r* __classcell__)rs@rr r s NE K$$$$$  [     NNNNNNNrr c$eZdZedZdS) MessageSourcec KdSzThis method is a coroutine.Nrr$loopsinks r create_sourcezMessageSource.create_source4 rN)r)r(r+rr9rrrr3r33s-**^***rr3c.eZdZdZdZedZdS)Sensorz+ Sensor is alias to MessageSource. c>K|||d{VSr5) create_sensorr6s rr9zSensor.create_source>s.''d333333333rc KdSr5rr6s rr>zSensor.create_sensorBr:rN)r)r(r+__doc__r9rr>rrrr<r<9sH444**^***rr<c<eZdZdZdZdZdZdZedZ dS)LogStreamReaderNic \K||_||_d|_|jsdSdddd|jf|_t j|jt jt jt jd|j dd{V|_ | | |j j dS)Nz /usr/bin/tailz --follow=namez-n0z--retryr)stdinstdoutstderrbufsizelimit)_loop_sink_cmd source_fileasynciocreate_subprocess_exec subprocessDEVNULLPIPE_LIMIT_child_process create_task_infinite_read_and_proceedrEr6s rr>zLogStreamReader.create_sensorOs     F        %,$B Y$?%+ % % %          + +D,?,F G G     rcpK|j|jdc}|_td|tt5|jdddn #1swxYwY|jd{V}td||dSdS)NzTerminating child process [%s]z,Terminated child process [%s] with code [%d])rKloggerdebugrProcessLookupErrorrSkillwait)r$cmdrcs rr%zLogStreamReader.shutdownns 9 !YNC LL93 ? ? ?,-- + +#((*** + + + + + + + + + + + + + + +*//11111111B LL>R      ! sA..A25A2cKtN)NotImplementedError)r$ stream_readers rrUz*LogStreamReader._infinite_read_and_proceed}s !!r) r)r(r+rLrRrKr>r%rrUrrrrBrBGs^KF D   >   ""^"""rrB) metaclassc>eZdZeddZdZdS)BaseMessageProcessor)maxsizecg}t|D]\}|drt||}t|r%t |dr||]|S)N__decorated_for_process_message)dir startswithgetattrcallablehasattrr)r$rvattr_strfuncs r_message_processorsz(BaseMessageProcessor._message_processorss D   H""3'' 4**D~~ '6##  $ rcKtd|||D],}||d{V}t|tr|cS-dS)NzDispatching %r through %r...)rWrXrr isinstancer )r$messagecororesults rprocess_messagez$BaseMessageProcessor.process_messages 3WdCCC,,..  D4==((((((F&'**     rN)r)r(r+rrrrxrrrrdrdsJYq   rrdcLeZdZGddZejZedZdS) MessageSinkcVeZdZdZdZdZdZdZdZdZ dZ d Z d Z e Z d Zd Zd ZdZdZdZdZdS)MessageSink.ProcessingOrder (27<FPQZrxiN)r)r(r+PRE_PROCESS_MESSAGELFDIGNORE_MESSAGEUNBLOCK_FROM_SUBNETCHECK_IP_IN_GRAYLISTGRAYLIST_TIMEOUTGRAYLIST_DB_FIXUPIMPORT_EXPORT_WBLIST ML_PREDICTIONDEFAULTIPSET_PROTECTORWEBSHIELD_PROTECTORWHITELIST_UNBLOCKEDSYNCLIST_UPDATE POST_ACTION EVENT_HOOK ICONTACT_SENTPOST_PROCESS_MESSAGErrrProcessingOrderr|ss  !! !     "rrc KdSr_r)r$r7s r create_sinkzMessageSink.create_sinks  rN)r)r(r+rrPROCESSING_ORDERrrrrrrzrzsa"#"#"#"#"#"#"#"#J'.  ^   rrz) async_lockcfd}|S)a @expect decorator for MessageSink.dosmth(message) async methods. MessageSink method will be called by MessageSink.process_message() if message_type and expect_fields match the message ones. @expect's can be stacked together and decision whether to call decorated coro is made by evaluating stacked @expect's with logical OR: @expect(MessageType.SensorAlert) # -- OR -- @expect(MessageType.SensorIncident, plugin_id='ossec') def protect(message): ... ctdddr#tdt fd}|_|S)Nr)rhz{coro} is not publicrvc2K  fd}dfd|rىdurd{V |d{V}durBttjr(rnV#t $rI}ttjr(r|d}~wwxYw|Sr|d{VSdS)Nc~to,tfdDS)Nc3PK|] \}}||kV!dSr_)get)rkvrus r zMexpect..decorate..decorated..match..sOAA,0AqGKKNNa'AAAAAAr)rtallitems) expect_fieldsru message_typesrmatchz:expect..decorate..decorated..matchs_!'<88SAAAA4A4G4G4I4IAAA>>rc"t|dS)Nri)rnrs r is_stackedz?expect..decorate..decorated..is_stackedst%EFFFrc>|r|jS|Sr_)ri)rvrterminals rrz=expect..decorate..decorated..terminals.:d##I#8D$GHHH rTF)acquirertr Lockablelockedrelease Exception) r$rurrwexcrrrrvrrs ` @@r decoratedz+expect..decorate..decorateds         G G G       uww %%!//+++++++++*#188D>>$#@#@@@@@@@F#e++&w 0DEE,#NN,,, )))!"7K,@AA*#NN,,* )))I  z$ 1!T$0000000004sB"" C5,AC00C5)rlrk TypeErrorformatrri)rvrrrrs` rdecoratezexpect..decorates 4R ( ( 3 3C 8 8 F299t9DDEE E t& & & & & & &  & P48 0rr)rrrrs``` rexpectrs0.......` Orc:t||S)zlRegister class as a plugin. >>> @thisguy >>> class ConcreteSink (MessageSink): >>> ... )_plugin_registryadd) pluginclss rthisguyr s### rctS)z*Enumerate classobj for registered plugins.)rrrr theseguysrs r)!rMrloggingrOabcrrr contextlibr functoolsrr"defence360agent.contracts.messagesr r defence360agent.utilsr getLoggerr)rWobjectr r3r<rBrdrzrsetrrrrrrrs,,,,,,,,,,&&&&&&&&CCCCCCCC''''''  8 $ $!N!N!N!N!N!N!N!NH*****J*** * * * * *]C * * *8"8"8"8"8"f8"8"8"8"v,* * * * * *2C* * * Z&*?????D355rdefence360agent/contracts/__pycache__/plugins.cpython-311.pyc0000644000000000000000000003210300000000000021140 0ustar r_j pddlZddlZddlZddlZddlmZmZmZddlm Z ddl m Z m Z ddl mZmZddlmZejeZGddeZGd d eeZGd d eeZGd deeZGddZGddeeeZdddZeZdZdZ dS)N)ABCABCMetaabstractmethod)suppress) lru_cachewraps)Message MessageType)ScopecZeZdZejZdZdZgZfdZ e dZ dZ dZ xZS) BasePlugindTc ntjdi||j|dS)N)super__init_subclass__ _subclassesappend)clskwargs __class__s V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/plugins.pyrzBasePlugin.__init_subclass__s<!!++F+++ s#####c$d|jDS)Nc:g|]}tj||Sr)inspect isabstract).0plugins r z1BasePlugin.get_active_plugins..s9   %f--    r)r)rs rget_active_pluginszBasePlugin.get_active_pluginss%  /    rc KdS)aZShutdown plugin's subsystems, cancel running tasks, clean iptables (if plugin is protector). It should be safe to assume that it is called after corresponding create_source if applicable. It is called only from the shutdown task that runs at most once, meaning shutdown() is never called twice. Nrselfs rshutdownzBasePlugin.shutdown"s  rc8|jjd|jjS)N.)r __module____name__r#s r__repr__zBasePlugin.__repr__/s .333T^5L5LMMr)r)r( __qualname__r AV_IM360SCOPESHUTDOWN_PRIORITYAVAILABLE_ON_FREEMIUMrr classmethodr!r%r* __classcell__)rs@rr r s NE K$$$$$  [     NNNNNNNrr c$eZdZedZdS) MessageSourcec KdSzThis method is a coroutine.Nrr$loopsinks r create_sourcezMessageSource.create_source4 rN)r)r(r+rr9rrrr3r33s-**^***rr3c.eZdZdZdZedZdS)Sensorz+ Sensor is alias to MessageSource. c>K|||d{VSr5) create_sensorr6s rr9zSensor.create_source>s.''d333333333rc KdSr5rr6s rr>zSensor.create_sensorBr:rN)r)r(r+__doc__r9rr>rrrr<r<9sH444**^***rr<c<eZdZdZdZdZdZdZedZ dS)LogStreamReaderNic \K||_||_d|_|jsdSdddd|jf|_t j|jt jt jt jd|j dd{V|_ | | |j j dS)Nz /usr/bin/tailz --follow=namez-n0z--retryr)stdinstdoutstderrbufsizelimit)_loop_sink_cmd source_fileasynciocreate_subprocess_exec subprocessDEVNULLPIPE_LIMIT_child_process create_task_infinite_read_and_proceedrEr6s rr>zLogStreamReader.create_sensorOs     F        %,$B Y$?%+ % % %          + +D,?,F G G     rcpK|j|jdc}|_td|tt5|jdddn #1swxYwY|jd{V}td||dSdS)NzTerminating child process [%s]z,Terminated child process [%s] with code [%d])rKloggerdebugrProcessLookupErrorrSkillwait)r$cmdrcs rr%zLogStreamReader.shutdownns 9 !YNC LL93 ? ? ?,-- + +#((*** + + + + + + + + + + + + + + +*//11111111B LL>R      ! sA..A25A2cKtN)NotImplementedError)r$ stream_readers rrUz*LogStreamReader._infinite_read_and_proceed}s !!r) r)r(r+rLrRrKr>r%rrUrrrrBrBGs^KF D   >   ""^"""rrB) metaclassc>eZdZeddZdZdS)BaseMessageProcessor)maxsizecg}t|D]\}|drt||}t|r%t |dr||]|S)N__decorated_for_process_message)dir startswithgetattrcallablehasattrr)r$rvattr_strfuncs r_message_processorsz(BaseMessageProcessor._message_processorss D   H""3'' 4**D~~ '6##  $ rcKtd|||D],}||d{V}t|tr|cS-dS)NzDispatching %r through %r...)rWrXrr isinstancer )r$messagecororesults rprocess_messagez$BaseMessageProcessor.process_messages 3WdCCC,,..  D4==((((((F&'**     rN)r)r(r+rrrrxrrrrdrdsJYq   rrdcLeZdZGddZejZedZdS) MessageSinkcVeZdZdZdZdZdZdZdZdZ dZ d Z d Z e Z d Zd Zd ZdZdZdZdZdS)MessageSink.ProcessingOrder (27<FPQZrxiN)r)r(r+PRE_PROCESS_MESSAGELFDIGNORE_MESSAGEUNBLOCK_FROM_SUBNETCHECK_IP_IN_GRAYLISTGRAYLIST_TIMEOUTGRAYLIST_DB_FIXUPIMPORT_EXPORT_WBLIST ML_PREDICTIONDEFAULTIPSET_PROTECTORWEBSHIELD_PROTECTORWHITELIST_UNBLOCKEDSYNCLIST_UPDATE POST_ACTION EVENT_HOOK ICONTACT_SENTPOST_PROCESS_MESSAGErrrProcessingOrderr|ss  !! !     "rrc KdSr_r)r$r7s r create_sinkzMessageSink.create_sinks  rN)r)r(r+rrPROCESSING_ORDERrrrrrrzrzsa"#"#"#"#"#"#"#"#J'.  ^   rrz) async_lockcfd}|S)a @expect decorator for MessageSink.dosmth(message) async methods. MessageSink method will be called by MessageSink.process_message() if message_type and expect_fields match the message ones. @expect's can be stacked together and decision whether to call decorated coro is made by evaluating stacked @expect's with logical OR: @expect(MessageType.SensorAlert) # -- OR -- @expect(MessageType.SensorIncident, plugin_id='ossec') def protect(message): ... ctdddr#tdt fd}|_|S)Nr)rhz{coro} is not publicrvc2K  fd}dfd|rىdurd{V |d{V}durBttjr(rnV#t $rI}ttjr(r|d}~wwxYw|Sr|d{VSdS)Nc~to,tfdDS)Nc3PK|] \}}||kV!dSr_)get)rkvrus r zMexpect..decorate..decorated..match..sOAA,0AqGKKNNa'AAAAAAr)rtallitems) expect_fieldsru message_typesrmatchz:expect..decorate..decorated..matchs_!'<88SAAAA4A4G4G4I4IAAA>>rc"t|dS)Nri)rnrs r is_stackedz?expect..decorate..decorated..is_stackedst%EFFFrc>|r|jS|Sr_)ri)rvrterminals rrz=expect..decorate..decorated..terminals.:d##I#8D$GHHH rTF)acquirertr Lockablelockedrelease Exception) r$rurrwexcrrrrvrrs ` @@r decoratedz+expect..decorate..decorateds         G G G       uww %%!//+++++++++*#188D>>$#@#@@@@@@@F#e++&w 0DEE,#NN,,, )))!"7K,@AA*#NN,,* )))I  z$ 1!T$0000000004sB"" C5,AC00C5)rlrk TypeErrorformatrri)rvrrrrs` rdecoratezexpect..decorates 4R ( ( 3 3C 8 8 F299t9DDEE E t& & & & & & &  & P48 0rr)rrrrs``` rexpectrs0.......` Orc:t||S)zlRegister class as a plugin. >>> @thisguy >>> class ConcreteSink (MessageSink): >>> ... )_plugin_registryadd) pluginclss rthisguyr s### rctS)z*Enumerate classobj for registered plugins.)rrrr theseguysrs r)!rMrloggingrOabcrrr contextlibr functoolsrr"defence360agent.contracts.messagesr r defence360agent.utilsr getLoggerr)rWobjectr r3r<rBrdrzrsetrrrrrrrs,,,,,,,,,,&&&&&&&&CCCCCCCC''''''  8 $ $!N!N!N!N!N!N!N!NH*****J*** * * * * *]C * * *8"8"8"8"8"f8"8"8"8"v,* * * * * *2C* * * Z&*?????D355rdefence360agent/contracts/__pycache__/sentry.cpython-311.opt-1.pyc0000644000000000000000000001366100000000000021752 0ustar r_j\ JddlmZddlmZmZmZddlmZddlm Z dZ e dZ e dZ da d Zd ed dfd Zd ed dfdZded dfdZded dfdZded dfdZdedzd dfdZdedzd dfdZded dfdZded dfdZded dfdZd efdZded efdZd!d ZdS)")Path)DEVNULLCalledProcessError check_output)Any)stub_unexpected_errorc t|t}n#ttf$rYdSwxYw|ddS)N)stderrzutf-8ignore)errors)rrFileNotFoundErrorrdecodestrip)cmdouts U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/sentry.py_run_cmdr sg3w/// 1 2tt ::gh: / / 5 5 7 77s ..ctdg}|r|Stdg}|r|Stgd}|r|SdS)Nzsystemd-detect-virtz virt-what) dmidecodez-szsystem-manufacturerzfail to detect)r) systemd_virt virt_whatdemicodes r_get_virtualization_typerse2344L+''IBBBCCH  cBddl}|jdzS)Nri)psutilvirtual_memorytotal)rs r_get_total_ramr#s%MMM  " " (E 11rNctjddlm}idddddddt|jddddd t d dd dd t d dddddddddddatS)Nr OsReleaseInfo av_version core_versionversion os_detailsip hosting_panel total_ramfirewallstrategyvirtualization server_idiaidname test_build_idtest_build_job_idtest_parent_build_id)_TAGSdefence360agent.utilsr"r pretty_namerrr!s r_tagsr6-s }777777 $ D  t  J/ 0IJJLL  $  T   ))      688    D  D  T   #D! $ Lrr*returnc(|td<dS)Nr*r6)r*s rset_firewall_typer:G"EGGJrpanelc(|td<dS)Nr(r9)r<s rset_hosting_panelr>Ks$EGGOrr+c(|td<dS)Nr+r9)r+s r set_strategyr@Or;rr'c(|td<dS)Nr'r9)r's rset_iprBSsEGGDMMMrproductc(|td<dS)Nr/r9)rCs rset_product_namerEWsEGGFOOOridc(|td<dS)Nr-r9)rFs r set_server_idrH[sEGGKrr.c(|td<dS)Nr.r9)r.s rset_iaidrJ_sEGGFOOOrr%c(|td<dS)Nr%r9r%s r set_versionrMcs EGGIrc(|td<dS)Nr#r9rLs rset_av_versionrOgs#EGGLrc(|td<dS)Nr$r9rLs rset_core_versionrQks%EGGNrcBtSN)r6copyrrtagsrVos 77<<>>rr/c*t|SrSr9)r/s rtagrXss 774=rc,tddftddftddffD]`\}}|rG |t |<P#t $rY\wxYwadS)z2Set tags for sentry events about test environment.z/var/imunify360/TEST_BUILD_IDr0z!/var/imunify360/TEST_BUILD_JOB_IDr1z$/var/imunify360/TEST_PARENT_BUILD_IDr2N)rexists read_textrr6 Exception) file_namerXs r set_test_envr^ws 0 1 1  4 5 5  7 8 8 "   3       (2244::<<      s 5B BB)r7N)pathlibr subprocessrrrtypingrr4rrrrr3r6strr:r>r@rBrErHrJrMrOrQdictrVrXr^rUrrrdso@@@@@@@@@@877777888    222 4######%S%T%%%%#3#4####stcdcDjT3:$!!!!!!$C$D$$$$&c&d&&&&dccrdefence360agent/contracts/__pycache__/sentry.cpython-311.pyc0000644000000000000000000001366100000000000021013 0ustar r_j\ JddlmZddlmZmZmZddlmZddlm Z dZ e dZ e dZ da d Zd ed dfd Zd ed dfdZded dfdZded dfdZded dfdZdedzd dfdZdedzd dfdZded dfdZded dfdZded dfdZd efdZded efdZd!d ZdS)")Path)DEVNULLCalledProcessError check_output)Any)stub_unexpected_errorc t|t}n#ttf$rYdSwxYw|ddS)N)stderrzutf-8ignore)errors)rrFileNotFoundErrorrdecodestrip)cmdouts U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/contracts/sentry.py_run_cmdr sg3w/// 1 2tt ::gh: / / 5 5 7 77s ..ctdg}|r|Stdg}|r|Stgd}|r|SdS)Nzsystemd-detect-virtz virt-what) dmidecodez-szsystem-manufacturerzfail to detect)r) systemd_virt virt_whatdemicodes r_get_virtualization_typerse2344L+''IBBBCCH  cBddl}|jdzS)Nri)psutilvirtual_memorytotal)rs r_get_total_ramr#s%MMM  " " (E 11rNctjddlm}idddddddt|jddddd t d dd dd t d dddddddddddatS)Nr OsReleaseInfo av_version core_versionversion os_detailsip hosting_panel total_ramfirewallstrategyvirtualization server_idiaidname test_build_idtest_build_job_idtest_parent_build_id)_TAGSdefence360agent.utilsr"r pretty_namerrr!s r_tagsr6-s }777777 $ D  t  J/ 0IJJLL  $  T   ))      688    D  D  T   #D! $ Lrr*returnc(|td<dS)Nr*r6)r*s rset_firewall_typer:G"EGGJrpanelc(|td<dS)Nr(r9)r<s rset_hosting_panelr>Ks$EGGOrr+c(|td<dS)Nr+r9)r+s r set_strategyr@Or;rr'c(|td<dS)Nr'r9)r's rset_iprBSsEGGDMMMrproductc(|td<dS)Nr/r9)rCs rset_product_namerEWsEGGFOOOridc(|td<dS)Nr-r9)rFs r set_server_idrH[sEGGKrr.c(|td<dS)Nr.r9)r.s rset_iaidrJ_sEGGFOOOrr%c(|td<dS)Nr%r9r%s r set_versionrMcs EGGIrc(|td<dS)Nr#r9rLs rset_av_versionrOgs#EGGLrc(|td<dS)Nr$r9rLs rset_core_versionrQks%EGGNrcBtSN)r6copyrrtagsrVos 77<<>>rr/c*t|SrSr9)r/s rtagrXss 774=rc,tddftddftddffD]`\}}|rG |t |<P#t $rY\wxYwadS)z2Set tags for sentry events about test environment.z/var/imunify360/TEST_BUILD_IDr0z!/var/imunify360/TEST_BUILD_JOB_IDr1z$/var/imunify360/TEST_PARENT_BUILD_IDr2N)rexists read_textrr6 Exception) file_namerXs r set_test_envr^ws 0 1 1  4 5 5  7 8 8 "   3       (2244::<<      s 5B BB)r7N)pathlibr subprocessrrrtypingrr4rrrrr3r6strr:r>r@rBrErHrJrMrOrQdictrVrXr^rUrrrdso@@@@@@@@@@877777888    222 4######%S%T%%%%#3#4####stcdcDjT3:$!!!!!!$C$D$$$$&c&d&&&&dccrdefence360agent/contracts/config.py0000644000000000000000000014017400000000000014374 0ustar """ All the config settings for defence360 in one place """ import functools import logging import os from abc import abstractmethod from bisect import bisect_left, bisect_right from contextvars import ContextVar from copy import deepcopy from datetime import datetime, timedelta from enum import Enum from pathlib import Path from typing import ( Any, Callable, Dict, List, Mapping, Optional, Protocol, Sequence, Tuple, Union, _ProtocolMeta, ) from cerberus import Validator from defence360agent.contracts.config_provider import ( CachedConfigReader, ConfigError, ConfigReader, UserConfigReader, WriteOnlyConfigReader, ) from defence360agent.feature_management.checkers import ( config_cleanup as fm_config_cleanup, ) from defence360agent._version import __version__ as core_version from defence360agent.utils import Singleton, dict_deep_update, importer av_version = importer.get( module="imav._version", name="__version__", default=None ) logger = logging.getLogger(__name__) ANTIVIRUS_MODE = not importer.exists("im360") # feature flag for those clients who want to test Myimunify FREEMIUM_FEATURE_FLAG = "/var/imunify360/myimunify-freemium.flag" MY_IMUNIFY_KEY = "MY_IMUNIFY" _version = importer.get( module="im360._version", name="__version__", default=av_version ) AGENT_CONF = "../." CONFIG_VALIDATORS_DIR_PATH = Path( os.environ.get( "IM360_CONFIG_SCHEMA_PATH", "/opt/imunify360/venv/share/imunify360/config_schema/", ) ) # TODO: remove after av-7.16.0 release NOTIFY, CLEANUP = "notify", "cleanup" NONE, DAY, WEEK, MONTH = "none", "day", "week", "month" DEFAULT_INTENSITY_CPU = 2 DEFAULT_INTENSITY_IO = 2 DEFAULT_INTENSITY_RAM = 2048 DEFAULT_INTENSITY_RESIDENT_RAM = 2048 DEFAULT_RESOURCE_MANAGEMENT_CPU_LIMIT = 2 DEFAULT_RESOURCE_MANAGEMENT_IO_LIMIT = 2 DEFAULT_RESOURCE_MANAGEMENT_RAM_LIMIT = 500 MODSEC_RULESET_FULL = "FULL" MODSEC_RULESET_MINIMAL = "MINIMAL" _DOS_DETECTOR_DEFAULT_LIMIT = 250 _DOS_DETECTOR_MIN_LIMIT = 1 _DOS_DETECTOR_MIN_INTERVAL = 1 PORT_BLOCKING_MODE_DENY = "DENY" PORT_BLOCKING_MODE_ALLOW = "ALLOW" DO_NOT_MODIFY_DISCLAMER = """\ ############################################################################ # DO NOT MODIFY THIS FILE!!! # # USE /etc/sysconfig/imunify360/imunify360.config.d/ TO OVERRIDE DEFAULTS # ############################################################################ """ DEFAULT_CONFIG_DISCLAMER = """\ ############################################################################ # DO NOT MODIFY THIS FILE!!! # # USE /etc/sysconfig/imunify360/imunify360.config.d/ TO OVERRIDE DEFAULTS # # This is an example of default values only # # Changing this file will have no effect # ############################################################################ """ CPANEL, PLESK, DIRECTADMIN = "cpanel", "plesk", "directadmin" ACRONIS, R1SOFT, CLUSTERLOGICS = "acronis", "r1soft", "clusterlogics" SAMPLE_BACKEND = "sample" CLOUDLINUX, CLOUDLINUX_ON_PREMISE = "cloudlinux", "cloudlinux_on_premise" GENERIC_SENSOR_SOCKET_PATH = "/var/run/defence360agent/generic_sensor.sock.2" def int_from_envvar(var: str, default: int, env: Mapping = os.environ) -> int: try: return int(env[var]) except KeyError: return default except ValueError as e: raise ValueError("{}: integer required".format(var)) from e def bool_from_envvar( var: str, default: bool, env: Mapping = os.environ ) -> bool: TRUE_VALS = ("true", "t", "yes", "y", "1") FALSE_VALS = ("false", "f", "no", "n", "0") try: val = env[var] except KeyError: return default else: val = val.lower() if val in TRUE_VALS: return True if val in FALSE_VALS: return False raise ValueError( "{}: should be one of {}".format(var, TRUE_VALS + FALSE_VALS) ) def _self_rel2abs(relpath): return os.path.join(os.path.dirname(__file__), relpath) def conf_rel2abs(relpath): return os.path.join(os.path.dirname(_self_rel2abs(AGENT_CONF)), relpath) def _slurp_file(path): """Returns content for existing file, otherwise None""" try: with open(path, "r") as f: return f.read().strip() except OSError: return None def choose_value_from_config( section, option, username: str | None = None ) -> Tuple[Any, str | None]: """ Choose action for config option by checking EndUser's Imunify360 config and Admin config. Admins config applies only if EndUser didn't set the default action """ user_config = ConfigFile(username=username).config_to_dict() user_section = user_config.get(section) if user_section is not None: user_value = user_section.get(option) if user_value is not None: return user_value, username logger.debug("Cannot read %s:%s from user config", section, option) root_config = ConfigFile().config_to_dict() return root_config[section][option], UserType.ROOT def is_mi_freemium_license(): """ Just checks if this is server with MyImunify Freemium license """ return os.path.exists(FREEMIUM_FEATURE_FLAG) class FromConfig: def __init__(self, section, option=None, config_cls=None): self.section = section self.option = option self._config_cls = config_cls self._config_instance = None def __get__(self, instance, owner): if self._config_instance is None: if self._config_cls is None: self._config_instance = ConfigFile() else: self._config_instance = self._config_cls() section_value = self._config_instance.config_to_dict()[self.section] if self.option is not None: return section_value[self.option] return section_value class FromFlagFile: LOCATION = Path("/var/imunify360") def __init__(self, name, *, coerce=bool, default=...): self.name = name self.coerce = coerce self.default = default def __get__(self, instance, owner): path = self.LOCATION / self.name if path.exists(): return self.coerce(path.read_text() or self.default) def _get_combined_validation_schema(*, root=True): func_name = "get_root_config" if root else "get_non_root_config" combined_schema = {} for module in importer.iter_modules([CONFIG_VALIDATORS_DIR_PATH]): get_schema = getattr(module, func_name, lambda: {}) schema = get_schema() dict_deep_update(combined_schema, schema, allow_overwrite=False) return combined_schema @functools.lru_cache(maxsize=1) def config_schema_root(): return _get_combined_validation_schema() @functools.lru_cache(maxsize=1) def config_schema_non_root(): return _get_combined_validation_schema(root=False) CONFIG_SCHEMA_CUSTOM_BILLING = { "CUSTOM_BILLING": { "type": "dict", "schema": { "upgrade_url": { "type": "string", "default": None, "nullable": True, }, "upgrade_url_360": { "type": "string", "default": None, "nullable": True, }, "billing_notifications": {"type": "boolean", "default": True}, "ip_license": {"type": "boolean", "default": True}, }, "default": {}, } } class ConfigValidationError(Exception): pass class Core: PRODUCT = "imunify360" NAME = "%s agent" % PRODUCT if not ANTIVIRUS_MODE else "imunify antivirus" AV_VERSION = av_version CORE_VERSION = core_version VERSION = _version # AV or IM360 API_BASE_URL = os.environ.get( "IMUNIFY360_API_URL", "https://api.imunify360.com" ) DEFAULT_SOCKET_TIMEOUT = 10 DIST = ".el8" FILE_UMASK = 0o007 TMPDIR = "/var/imunify360/tmp" MERGED_CONFIG_FILE_NAME = "imunify360-merged.config" MERGED_NONPRIVILEGED_CONFIG_FILE_NAME = ( "imunify360-merged-nonprivileged.config" ) USER_CONFIG_FILE_NAME = "imunify360.config" LOCAL_CONFIG_FILE_NAME = "imunify360.config" CONFIG_DIR = "/etc/imunify360" USER_CONFDIR = os.path.join(CONFIG_DIR, "user_config") GLOBAL_CONFDIR = "/etc/sysconfig/imunify360" MERGED_CONFIG_FILE_PATH = os.path.join( GLOBAL_CONFDIR, MERGED_CONFIG_FILE_NAME ) MERGED_NONPRIVILEGED_CONFIG_FILE_PATH = os.path.join( GLOBAL_CONFDIR, MERGED_NONPRIVILEGED_CONFIG_FILE_NAME ) MERGED_CONFIG_FILE_PERMISSION = 0o640 MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSION = 0o644 LOCAL_CONFIG_FILE_PATH = os.path.join(GLOBAL_CONFDIR, "imunify360.config") CONFIG_D_NAME = "imunify360.config.d" BACKUP_CONFIGFILENAME = ".imunify360.backup_config" HOOKS_CONFIGFILENAME = "hooks.yaml" CUSTOM_BILLING_CONFIGFILENAME = "custom_billing.config" INBOX_HOOKS_DIR = "/var/imunify360/hooks" SVC_NAME = ( "imunify360-agent" if not ANTIVIRUS_MODE else "imunify-antivirus" ) UNIFIED_ACCESS_LOGGER_CONFIGFILENAME = "unified-access-logger.conf" GO_FLAG_FILE = Path("/etc/sysconfig/imunify360/.go_agent") SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECS = 60 class IConfig(Protocol): @abstractmethod def config_to_dict( self, normalize: bool = True, force_read: bool = False ) -> dict: raise NotImplementedError @abstractmethod def dict_to_config( self, data: Mapping, validate: bool = True, normalize: bool = True, overwrite: bool = False, without_defaults: bool = False, ) -> None: raise NotImplementedError @abstractmethod def validate(self) -> None: raise NotImplementedError @abstractmethod def normalize( self, config: Mapping, without_defaults: bool = False ) -> dict: raise NotImplementedError @abstractmethod def modified_since(self, timestamp: Optional[float]) -> bool: raise NotImplementedError def get(self, section: str, option: Optional[str]) -> Any: if option: return self.config_to_dict().get(section, {}).get(option) return None def set(self, section: str, option: Optional[str], value: Any) -> None: if option: self.dict_to_config({section: {option: value}}) class IConfigFile(IConfig, Protocol): path: str class ProtocolSingleton(_ProtocolMeta, Singleton): """ Needed to avoid metaclass conflict when implementing protocols that are also Singletons """ class Normalizer: def __init__(self, validation_schema): self._config: Optional[Mapping] = None self._normalized_config: dict = {} self._schema = ConfigsValidator.get_validation_schema( validation_schema ) self._schema_without_defaults = self._get_schema_without_defaults( self._schema ) @classmethod def _get_schema_without_defaults(cls, _dict: Mapping) -> dict: return { key: cls._get_schema_without_defaults(value) if isinstance(value, dict) else value for key, value in _dict.items() if key not in ["default", "default_setter"] } @staticmethod def remove_null(config: Mapping) -> dict: new_config: Dict[str, Union[dict, List[Tuple]]] = {} for section, options in config.items(): # We only support dict for option removal if isinstance(options, dict): for option, value in options.items(): if value is not None: new_config.setdefault(section, {})[option] = value elif options: # Let cerberus coerce this to dict # and leave the None's as is new_config[section] = options return new_config @staticmethod def _normalize_with_schema(config: Mapping, schema) -> dict: validator = ConfigValidator(schema) normalized: Optional[dict] = validator.normalized(config) if validator.errors: raise ConfigValidationError(validator.errors) if normalized is None: raise ConfigValidationError(f"Cerberus returned None for {config}") return normalized def normalize(self, config: Mapping, without_defaults: bool) -> dict: schema = ( self._schema_without_defaults if without_defaults else self._schema ) if without_defaults: config = self.remove_null(config) return self._normalize_with_schema(config, schema) # Utilize cache if config == self._config: return self._normalized_config normalized = self._normalize_with_schema(config, schema) self._config = config self._normalized_config = normalized return self._normalized_config class Config(IConfig, metaclass=ProtocolSingleton): DISCLAIMER = "" def __init__( self, *, # FIXME: allow pathlib.Path path: str = None, config_reader: ConfigReader = None, validation_schema: Union[Mapping, Callable[[], Mapping]] = None, disclaimer: str = None, cached: bool = True, permissions: int = None, ): super().__init__() assert path or config_reader config_reader_cls = CachedConfigReader if cached else ConfigReader self._config_reader = config_reader or config_reader_cls( path, disclaimer=disclaimer or self.DISCLAIMER, permissions=permissions, ) self.path = self._config_reader.path self.validation_schema = validation_schema or config_schema_root self._normalizer = Normalizer(self.validation_schema) def __repr__(self): return ( "<{classname}(config_reader={config_reader!r}," " validation_schema={validation_schema!r})" ">" ).format( classname=self.__class__.__qualname__, config_reader=self._config_reader, validation_schema=self.validation_schema, ) def normalize(self, config: Mapping, without_defaults=False) -> dict: return self._normalizer.normalize(config, without_defaults) def config_to_dict(self, normalize=True, force_read: bool = False) -> dict: """ Converts config file to dict :return dict: dictionary (key (section) / value (options)) """ config = self._config_reader.read_config_file(force_read=force_read) if normalize: config = self.normalize(config) return deepcopy(config) def dict_to_config( self, data: Mapping, validate: bool = True, normalize: bool = True, overwrite: bool = False, without_defaults: bool = False, ) -> None: """ Converts dict to config file New options will be mixed in with old ones unless overwrite is specified :param dict data: dictionary (key (section) / value (options)) :param bool validate: indicates if we need validation :param bool normalize: normalize config :param overwrite: overwrite existing conf :param without_defaults: do not fill defaults :return: None """ if overwrite: self._dict_to_config_overwrite( data=data, validate=validate, normalize=normalize, without_defaults=without_defaults, ) else: self._dict_to_config( data=data, validate=validate, normalize=normalize, without_defaults=without_defaults, ) def _dict_to_config_overwrite( self, data, validate, normalize, without_defaults ): if validate: ConfigsValidator.validate(data, self.validation_schema) if normalize: data = self.normalize(data, without_defaults=without_defaults) self._config_reader.write_config_file(data) def _dict_to_config(self, data, validate, normalize, without_defaults): config = deepcopy(self._config_reader.read_config_file()) if dict_deep_update(config, data): if validate: ConfigsValidator.validate(config, self.validation_schema) if normalize: config = self.normalize( config, without_defaults=without_defaults ) self._config_reader.write_config_file(config) def validate(self): """ :raises ConfigsValidatorError """ try: config_dict = self._config_reader.read_config_file( ignore_errors=False ) except ConfigError as e: message = "Error during config validation" logger.error("%s: %s", message, e) raise ConfigsValidatorError({self: message}) from e try: ConfigsValidator.validate(config_dict, self.validation_schema) except ConfigValidationError as e: message = "Imunify360 config does not match the scheme" logger.error("%s: %s", message, e) raise ConfigsValidatorError({self: message}) from e def modified_since(self, timestamp: Optional[float]) -> bool: return self._config_reader.modified_since(timestamp) class UserConfig(Config): def __init__(self, *, username): self.username = username path = os.path.join( Core.USER_CONFDIR, username, Core.USER_CONFIG_FILE_NAME ) super().__init__( path=path, config_reader=UserConfigReader(path, username), validation_schema=config_schema_non_root, ) class SystemConfig(IConfig, metaclass=ProtocolSingleton): def __init__( self, *, local_config: Config = None, merged_config: Config = None, nonpriv_merged_config: Config = None, ): super().__init__() self._local_config = local_config or LocalConfig() self._merged_config = merged_config or MergedConfig() self._nonpriv_merged_config = ( nonpriv_merged_config or MergedNonPrivilegedConfig() ) def config_to_dict( self, normalize: bool = True, force_read: bool = False ) -> dict: return self._merged_config.config_to_dict( normalize=normalize, force_read=force_read ) def dict_to_config( self, data: Mapping, validate: bool = True, normalize: bool = True, overwrite: bool = False, without_defaults: bool = True, ) -> None: self._local_config.dict_to_config( data, validate=validate, normalize=normalize, overwrite=overwrite, without_defaults=without_defaults, ) def validate(self) -> None: self._merged_config.validate() self._nonpriv_merged_config.validate() def normalize( self, config: Mapping, without_defaults: bool = False ) -> dict: return self._merged_config.normalize( config=config, without_defaults=without_defaults ) def modified_since(self, timestamp: Optional[float]) -> bool: return self._merged_config.modified_since(timestamp) def config_file_factory( username: Optional[Union[str, int]] = None, path: Optional[str] = None ) -> IConfig: if username and not isinstance(username, int): return UserConfig(username=username) elif path: return Config(path=path) else: return SystemConfig() # TODO: move all layer related functions to another module def any_layer_modified_since(timestamp) -> bool: merger = Merger(Merger.get_layer_names()) for layer in merger.layers: if layer.modified_since(timestamp): return True return False MergedConfig = functools.partial( Config, config_reader=WriteOnlyConfigReader( path=Core.MERGED_CONFIG_FILE_PATH, disclaimer=DO_NOT_MODIFY_DISCLAMER, permissions=Core.MERGED_CONFIG_FILE_PERMISSION, ), ) MergedNonPrivilegedConfig = functools.partial( Config, config_reader=WriteOnlyConfigReader( path=Core.MERGED_NONPRIVILEGED_CONFIG_FILE_PATH, disclaimer=DO_NOT_MODIFY_DISCLAMER, permissions=Core.MERGED_NONPRIVILEGED_CONFIG_FILE_PERMISSION, ), ) class LocalConfig(Config): """ Config (/etc/sysconfig/imunify360/imunify360.config) should contain options changed by a customer only """ def __init__( self, *, path=Core.LOCAL_CONFIG_FILE_PATH, # overrides the parent default value config_reader: ConfigReader = None, validation_schema: Union[Mapping, Callable[[], Mapping]] = None, disclaimer: str = None, cached=False, # overrides the parent default value ): super().__init__( path=path, config_reader=config_reader, validation_schema=validation_schema, disclaimer=disclaimer, cached=cached, ) def dict_to_config( self, data: Mapping, validate: bool = True, normalize: bool = True, overwrite: bool = False, without_defaults: bool = True, # overrides the parent default value ) -> None: return super().dict_to_config( data, validate=validate, normalize=normalize, overwrite=overwrite, without_defaults=without_defaults, ) class BaseMerger: DIR = os.path.join(Core.GLOBAL_CONFDIR, Core.CONFIG_D_NAME) LOCAL_CONFIG_NAME = "90-local.config" def __init__(self, names, include_defaults=False): self._include_defaults = include_defaults self.layers = [ Config(path=os.path.join(self.DIR, name)) for name in names ] @classmethod def get_layer_names(cls): return sorted(os.listdir(cls.DIR)) if os.path.isdir(cls.DIR) else [] def configs_to_dict(self, force_read=False): layer_dict_list = [] if self._include_defaults: defaults = Normalizer(config_schema_root).normalize( {}, without_defaults=False ) layer_dict_list.append(defaults) layer_dict_list += [ layer.config_to_dict(normalize=False, force_read=force_read) for layer in self.layers ] return self._build_effective_config(layer_dict_list) @classmethod def _build_effective_config(cls, layer_dict_list: list): effective: Dict[str, dict] = {} for layer_dict in layer_dict_list: for section, options in layer_dict.items(): if options is None: continue if section not in effective: effective[section] = {} for option, value in options.items(): if value is not None: effective[section][option] = value return effective class MutableMerger(BaseMerger): def __init__(self, names: Sequence): idx = bisect_left(names, self.LOCAL_CONFIG_NAME) names = names[:idx] super().__init__(names, include_defaults=True) class ImmutableMerger(BaseMerger): def __init__(self, names: Sequence): idx = bisect_right(names, self.LOCAL_CONFIG_NAME) names = names[idx:] super().__init__(names, include_defaults=False) class NonBaseMerger(BaseMerger): def __init__(self, names: Sequence): super().__init__(names, include_defaults=False) class Merger(BaseMerger): NONPRIVILEGED_SETTINGS = { "PROACTIVE_DEFENCE": None, # entire section "PERMISSIONS": [ "user_override_proactive_defense", ], "INCIDENT_LOGGING": [ "num_days", "limit", ], "ERROR_REPORTING": [ "enable", ], # modsec_scan_wrapper.sh reads this before invoking real malware # scanning; surfacing it here lets us lock down the privileged file. "MALWARE_SCANNING": [ "enable_scan_modsec", ], } def __init__(self, names): super().__init__(names, include_defaults=True) @classmethod def update_merged_config(cls): merger = cls(cls.get_layer_names()) config_dict = merger.configs_to_dict() # DEF-42492: ConfigReader silently swallows FileNotFoundError as # {}, so a layer file that came from get_layer_names() but is # unreadable when opened (a TOCTOU race during yum upgrades or # similar) would silently degrade the merged config to schema # defaults. Re-check existence post-read and abort the persistent # merged-config write if any layer is missing — the previously- # good imunify360-merged.config is preserved instead. The check # is scoped to update_merged_config rather than BaseMerger so it # does not break read-only callers (migrations, RPC endpoints). # lexists() (matching listdir's no-symlink-follow semantics) so a # broken symlink — entry present, target missing — is treated as # a legitimate empty layer rather than a disappeared one. The # agent integration image ships such a symlink intentionally # (10_on_first_install.config -> nonexistent target). for layer in merger.layers: if not os.path.lexists(layer.path): logger.warning( "Aborting merged config update: " "Config layer %s disappeared during merge", layer.path, ) return try: ConfigsValidator.validate(config_dict) except (ConfigsValidatorError, ConfigValidationError) as e: logger.warning("Config file is invalid! %s", e) else: # Re-normalize to apply coercers that depend on other config values # (e.g., user_override_proactive_defense depends on MY_IMUNIFY.enable) normalizer = Normalizer(config_schema_root) config_dict = normalizer.normalize( config_dict, without_defaults=False ) priv_dict, nonpriv_dict = cls._split_settings(config_dict) MergedConfig().dict_to_config(priv_dict, validate=False) MergedNonPrivilegedConfig().dict_to_config( nonpriv_dict, validate=False, normalize=False ) @classmethod def _split_settings(cls, config_dict: dict) -> tuple: """Split config into privileged and non-privileged parts. Non-privileged config is a subset of privileged config - both contain the same values for settings listed in NONPRIVILEGED_SETTINGS. """ priv_dict = deepcopy(config_dict) nonpriv_dict: Dict[str, dict] = {} for section, options in cls.NONPRIVILEGED_SETTINGS.items(): if section not in config_dict: continue if options is None: # None means entire section is copied to nonprivileged nonpriv_dict[section] = deepcopy(config_dict[section]) else: for option in options: if option in config_dict[section]: if section not in nonpriv_dict: nonpriv_dict[section] = {} nonpriv_dict[section][option] = config_dict[section][ option ] return priv_dict, nonpriv_dict class ConfigValidator(Validator): def __init__( self, *args, allow_unknown=ANTIVIRUS_MODE, purge_readonly=True, **kwargs, ): """ Initialises ConfigValidator(Validator) for more details on Validator params please check https://docs.python-cerberus.org/en/stable/validation-rules.html """ super().__init__( *args, allow_unknown=allow_unknown, purge_readonly=purge_readonly, **kwargs, ) def _normalize_coerce_user_override_pd_rules(self, value): if self.root_document.get("MY_IMUNIFY", {}).get("enable", False): return True return value class Packaging: DATADIR = "/opt/imunify360/venv/share/%s" % Core.PRODUCT class SimpleRpc: # how long to wait for the response from RPC server in seconds CLIENT_TIMEOUT = 3600 SOCKET_PATH = "/var/run/defence360agent/simple_rpc.sock" # end-user stuff NON_ROOT_SOCKET_PATH = "/var/run/defence360agent/non_root_simple_rpc.sock" TOKEN_FILE_TMPL = ".imunify360_token_{suffix}" # -r-------- TOKEN_MASK = 0o400 SOCKET_ACTIVATION = bool_from_envvar( "I360_SOCKET_ACTIVATION", ANTIVIRUS_MODE, ) INACTIVITY_TIMEOUT = int_from_envvar( "IMUNIFY360_INACTIVITY_TIMEOUT", int(timedelta(minutes=5).total_seconds()), ) MAX_CONCURRENT_CONNECTIONS = int_from_envvar( "I360_RPC_MAX_CONNECTIONS", 256 ) READ_TIMEOUT = int_from_envvar( "I360_RPC_READ_TIMEOUT", int(timedelta(minutes=5).total_seconds()), ) class Model: # type sqlite3 - sqlite only supported PATH = "/var/%s/%s.db" % (Core.PRODUCT, Core.PRODUCT) PROACTIVE_PATH = "/var/%s/proactive.db" % (Core.PRODUCT,) RESIDENT_PATH = "/var/%s/%s-resident.db" % (Core.PRODUCT, Core.PRODUCT) class FilesUpdate: # Check files update periodically PERIOD = timedelta(minutes=30).total_seconds() # Timeout for single Index update (group of files) DEF-11501 # It has to be less than watchdog timeout (60 min) TIMEOUT = timedelta(minutes=15).total_seconds() # Timeout for individual socket operations (connect, recv/read, etc.) # For instance ssl-handshake timeout == SOCKET_TIMEOUT # Than less the value than better, to do not wait to long SOCKET_TIMEOUT = 3 # seconds # Following settings applicable only for IM360: # File types that should be downloaded but not applied # to the system (e.g. for testing purposes) DISABLED = [] # How long to keep the files on the disk DAYS_TO_KEEP = 30 class CountryInfo: DB = "/var/imunify360/files/geo/v1/GeoLite2-Country.mmdb" LOCATIONS_DB = ( "/var/imunify360/files/geo/v1/GeoLite2-Country-Locations-en.csv" ) @staticmethod def country_subnets_file(country_code): return "/var/imunify360/files/geo/v1/CountrySubnets-{}.txt".format( country_code ) class Sentry: DSN = os.getenv( "IMUNITY360_SENTRY_DSN", _slurp_file("%s/sentry" % Packaging.DATADIR) ) ENABLE = FromConfig("ERROR_REPORTING", "enable") class Malware: SCAN_CHECK_PERIOD = 300 CONSECUTIVE_ERROR_LIMIT = 10 INOTIFY_SCAN_PERIOD = 60 CONFIG_CHECK_PERIOD = 30 CONFLICTS_CHECK_PERIOD = 300 MAX_TARGETS_PER_SCAN_TYPE = FromConfig( "MALWARE_SCANNING", "max_targets_per_scan_type" ) MAX_PATH_LEN = FromConfig("MALWARE_SCANNING", "max_path_len") INOTIFY_ENABLED = FromConfig("MALWARE_SCANNING", "enable_scan_inotify") PURE_SCAN = FromConfig("MALWARE_SCANNING", "enable_scan_pure_ftpd") SEND_FILES = FromConfig("MALWARE_SCANNING", "sends_file_for_analysis") CLOUD_ASSISTED_SCAN = FromConfig("MALWARE_SCANNING", "cloud_assisted_scan") RAPID_SCAN = FromConfig("MALWARE_SCANNING", "rapid_scan") CRONTABS_SCAN_ENABLED = FromConfig("MALWARE_SCANNING", "crontabs") SCANS_PATH = "/var/imunify360/aibolit/scans.pickle" FILE_PREVIEW_BYTES_NUM = 1024 * 100 # 100 KB CLEANUP_STORAGE = "/var/imunify360/cleanup_storage" CLEANUP_TRIM = FromConfig( section="MALWARE_CLEANUP", option="trim_file_instead_of_removal", ) CLEANUP_KEEP = FromConfig( section="MALWARE_CLEANUP", option="keep_original_files_days", ) SCAN_MODIFIED_FILES = FromConfig( section="MALWARE_SCANNING", option="scan_modified_files", ) MAX_SIGNATURE_SIZE_TO_SCAN = FromConfig( "MALWARE_SCANNING", "max_signature_size_to_scan" ) MAX_CLOUDSCAN_SIZE_TO_SCAN = FromConfig( "MALWARE_SCANNING", "max_cloudscan_size_to_scan" ) MAX_MRS_UPLOAD_FILE = FromConfig("MALWARE_SCANNING", "max_mrs_upload_file") RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY = FromConfig( "MALWARE_SCANNING", "rapid_scan_rescan_unchanging_files_frequency" ) HYPERSCAN = FromConfig("MALWARE_SCANNING", "hyperscan") DATABASE_SCAN_ENABLED = FromConfig("MALWARE_DATABASE_SCAN", "enable") CPANEL_SCAN_ENABLED = FromConfig("MALWARE_SCANNING", "enable_scan_cpanel") CLEANUP_DISABLE_CLOUDAV = FromFlagFile("disable_cloudav") MDS_DB_TIMEOUT = FromConfig("MALWARE_DATABASE_SCAN", "db_timeout") class MalwareTune: """ Experimental and testing-only purpose settings we don't want to expose to customers. """ USE_JSON_REPORT = FromFlagFile("use_json") NO_CHECK_KNOWN_HASHES = FromFlagFile("no_check_known_hashes") RAPID_SCAN_BASEDIR_OVERRIDE = FromFlagFile( "rapid_scan_basedir_override", coerce=Path, default="/home" ) NO_AUTO_UPGRADE = FromFlagFile("no_auto_upgrade") class MalwareScanScheduleInterval: NONE = NONE DAY = DAY WEEK = WEEK MONTH = MONTH class MalwareScanSchedule: CMD = "/usr/bin/imunify360-agent malware user scan --background" CRON_PATH = "/etc/cron.d/imunify_scan_schedule" CRON_STRING = """\ # DO NOT EDIT. AUTOMATICALLY GENERATED. 0 {0} {1} * {2} root {cmd} >/dev/null 2>&1 """ INTERVAL = FromConfig( section="MALWARE_SCAN_SCHEDULE", option="interval", ) HOUR = FromConfig( section="MALWARE_SCAN_SCHEDULE", option="hour", ) DAY_OF_WEEK = FromConfig( section="MALWARE_SCAN_SCHEDULE", option="day_of_week", ) DAY_OF_MONTH = FromConfig( section="MALWARE_SCAN_SCHEDULE", option="day_of_month", ) class MalwareScanIntensity: CPU = FromConfig( section="MALWARE_SCAN_INTENSITY", option="cpu", ) IO = FromConfig( section="MALWARE_SCAN_INTENSITY", option="io", ) RAM = FromConfig( section="MALWARE_SCAN_INTENSITY", option="ram", ) USER_CPU = FromConfig( section="MALWARE_SCAN_INTENSITY", option="user_scan_cpu", ) USER_IO = FromConfig( section="MALWARE_SCAN_INTENSITY", option="user_scan_io", ) USER_RAM = FromConfig( section="MALWARE_SCAN_INTENSITY", option="user_scan_ram", ) RESIDENT_RAM = FromConfig( section="MALWARE_SCAN_INTENSITY", option="resident_ram", ) class FileBasedResourceLimits: CPU = FromConfig( section="RESOURCE_MANAGEMENT", option="cpu_limit", ) IO = FromConfig( section="RESOURCE_MANAGEMENT", option="io_limit", ) RAM = FromConfig( section="RESOURCE_MANAGEMENT", option="ram_limit", ) class KernelCare: EDF = FromConfig( section="KERNELCARE", option="edf", ) def get_rapid_rescan_frequency(): value = Malware.RAPID_SCAN_RESCAN_UNCHANGING_FILES_FREQUENCY if value is None: freq = { MalwareScanScheduleInterval.NONE: 1, MalwareScanScheduleInterval.MONTH: 2, MalwareScanScheduleInterval.WEEK: 5, MalwareScanScheduleInterval.DAY: 10, } return freq.get(MalwareScanSchedule.INTERVAL, 1) return value class MalwareSignatures: _dir = "/var/imunify360/files/sigs/v1/" RFXN = os.path.join(_dir, "rfxn") i360 = os.path.join(_dir, "i360") AI_BOLIT_HOSTER = os.path.join(_dir, "aibolit", "ai-bolit-hoster-full.db") AI_BOLIT_HYPERSCAN = os.path.join(_dir, "aibolit", "hyperscan") MDS_AI_BOLIT_HOSTER = os.path.join( _dir, "aibolit", "mds-ai-bolit-hoster.db" ) PROCU_DB = os.path.join(_dir, "aibolit", "procu2.db") MDS_PROCU_DB = os.path.join(_dir, "aibolit", "mds-procu2.db") class Logger: MAX_LOG_FILE_SIZE = FromConfig( section="LOGGER", option="max_log_file_size", ) BACKUP_COUNT = FromConfig( section="LOGGER", option="backup_count", ) # directory mode for main log directory and all inner LOG_DIR_PERM = 0o700 # file mode for main log directory and all inner LOG_FILE_PERM = 0o660 class UserType: ROOT = "root" NON_ROOT = "non_root" # Set by RPC middleware to the authenticated caller; defaults to ROOT so # non-RPC paths (migrations, workers, direct CLI) keep admin semantics. caller_type: ContextVar[str] = ContextVar("caller_type", default=UserType.ROOT) class UIRole: CLIENT = "client" ADMIN = "admin" class NoCP: # path to script implementing No CP API CLIENT_SCRIPT = "/etc/imunify360/scripts/domains" # latest version of the API supported by agent LATEST_VERSION = 1 class CustomBillingConfig(Config): def __init__(self): path = os.path.join( "/etc/sysconfig/imunify360", Core.CUSTOM_BILLING_CONFIGFILENAME ) super().__init__( path=path, validation_schema=CONFIG_SCHEMA_CUSTOM_BILLING ) class CustomBilling: UPGRADE_URL = FromConfig( section="CUSTOM_BILLING", option="upgrade_url", config_cls=CustomBillingConfig, ) UPGRADE_URL_360 = FromConfig( section="CUSTOM_BILLING", option="upgrade_url_360", config_cls=CustomBillingConfig, ) NOTIFICATIONS = FromConfig( section="CUSTOM_BILLING", option="billing_notifications", config_cls=CustomBillingConfig, ) IP_LICENSE = FromConfig( section="CUSTOM_BILLING", option="ip_license", config_cls=CustomBillingConfig, ) class PermissionsConfig: USER_IGNORE_LIST = FromConfig( section="PERMISSIONS", option="user_ignore_list", ) ALLOW_MALWARE_SCAN = FromConfig( section="PERMISSIONS", option="allow_malware_scan", ) USER_OVERRIDE_MALWARE_ACTIONS = FromConfig( section="PERMISSIONS", option="user_override_malware_actions" ) USER_OVERRIDE_PROACTIVE_DEFENSE = FromConfig( section="PERMISSIONS", option="user_override_proactive_defense", ) ALLOW_LOCAL_MALWARE_IGNORE_LIST_MANAGEMENT = FromConfig( section="PERMISSIONS", option="allow_local_malware_ignore_list_management", ) USE_PLESK_SERVICE_PLAN = FromConfig( section="PERMISSIONS", option="use_plesk_service_plan", ) ALLOW_WP_WAF_RULES_MANAGEMENT = FromConfig( section="PERMISSIONS", option="allow_wp_waf_rules_management", ) class MyImunifyConfig: ENABLED = FromConfig( section="MY_IMUNIFY", option="enable", ) PURCHASE_PAGE_URL = FromConfig( section="MY_IMUNIFY", option="purchase_page_url", ) class ControlPanelConfig: SMART_ADVICE_ALLOWED = FromConfig( section="CONTROL_PANEL", option="smart_advice_allowed", ) ADVICE_EMAIL_NOTIFICATION = FromConfig( section="CONTROL_PANEL", option="advice_email_notification", ) def effective_user_config(admin_config, user_config): allowed_sections = [ "BACKUP_RESTORE", "MALWARE_CLEANUP", "MALWARE_SCANNING", "ERROR_REPORTING", "PROACTIVE_DEFENCE", "PERMISSIONS", "MY_IMUNIFY", "CONTROL_PANEL", "MALWARE_SCAN_SCHEDULE", "WORDPRESS", ] overridable = { ( "MALWARE_SCAN_SCHEDULE", "interval", ): PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS, ( "MALWARE_SCAN_SCHEDULE", "hour", ): PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS, ( "MALWARE_SCAN_SCHEDULE", "day_of_week", ): PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS, ( "MALWARE_SCAN_SCHEDULE", "day_of_month", ): PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS, ( "MALWARE_SCANNING", "default_action", ): PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS, ( "PROACTIVE_DEFENCE", "mode", ): PermissionsConfig.USER_OVERRIDE_PROACTIVE_DEFENSE, } admin_dict = admin_config.config_to_dict() user_dict = user_config.config_to_dict() # Users can disable WAF per-account when admin has it enabled, # but cannot re-enable it when admin has disabled it globally. overridable[("WORDPRESS", "waf_enabled")] = admin_dict.get( "WORDPRESS", {} ).get("waf_enabled", True) def normalize_section(section): admin_options = deepcopy(admin_dict.get(section, {})) user_options = deepcopy(user_dict.get(section, {})) resulting_dict = {} for option, admin_value in admin_options.items(): user_value = user_options.get(option) if ( user_value is not None # All options available to user are overridable by default and overridable.get((section, option), True) ): resulting_dict[option] = user_value else: resulting_dict[option] = admin_value return resulting_dict effective_config = { section: normalize_section(section) for section in allowed_sections } return fm_config_cleanup(effective_config, user_config.username) class HookEvents: IM360_EVENTS = ( AGENT, LICENSE, MALWARE_SCANNING, MALWARE_CLEANUP, MALWARE_DETECTED, ) = ( "agent", "license", "malware-scanning", "malware-cleanup", "malware-detected", ) IMAV_EVENTS = ( LICENSE, MALWARE_SCANNING, MALWARE_CLEANUP, MALWARE_DETECTED, ) EVENTS = IMAV_EVENTS if ANTIVIRUS_MODE else IM360_EVENTS class ConfigsValidatorError(Exception): def __init__(self, configs_to_errors: Dict[Config, str]): self.configs_to_errors = configs_to_errors def __repr__(self): errors = [] for config, error in self.configs_to_errors.items(): errors.append(f"{config!r}: {error}") return "\n".join(errors) class ConfigsValidator: """A class that has methods to validate configs bypassing the cache""" @classmethod def validate_system_config(cls): """ Validate merged config :raises ConfigsValidatorError """ SystemConfig().validate() @classmethod def validate_config_layers(cls): """ Validate all config layers, collect all errors :raises ConfigsValidatorError """ configs_to_errors = {} for layer in Merger(Merger.get_layer_names()).layers: try: layer.validate() except ConfigsValidatorError as e: configs_to_errors.update(e.configs_to_errors) if configs_to_errors: raise ConfigsValidatorError(configs_to_errors) @classmethod def validate( cls, config_dict: dict, validation_schema: Union[dict, Callable] = config_schema_root, ) -> None: """ Validate config represented by a dict :param config_dict: config to validate :param validation_schema: schema to validate config against :raises ConfigValidationError """ schema = cls.get_validation_schema(validation_schema) v = ConfigValidator(schema) if not v.validate(config_dict): raise ConfigValidationError(v.errors) @staticmethod def get_validation_schema( validation_schema: Union[Mapping, Callable], ) -> Mapping: if callable(validation_schema): return validation_schema() return validation_schema ConfigFile = config_file_factory class AdminContacts: ENABLE_ICONTACT_NOTIFICATIONS = FromConfig( section="ADMIN_CONTACTS", option="enable_icontact_notifications", ) class IContactMessageType(str, Enum): MALWARE_FOUND = "MalwareFound" SCAN_NOT_SCHEDULED = "ScanNotScheduled" GENERIC = "Generic" def __str__(self): return self.value CONFIG_SCHEMA_BACKUP_SYSTEM = { "BACKUP_SYSTEM": { "type": "dict", "schema": { "enabled": { "type": "boolean", "default": False, }, "backup_system": { "type": "string", "default": None, "nullable": True, "allowed": [ CPANEL, PLESK, R1SOFT, ACRONIS, CLOUDLINUX, DIRECTADMIN, CLOUDLINUX_ON_PREMISE, CLUSTERLOGICS, SAMPLE_BACKEND, ], }, }, "default": {}, } } class BackupConfig(Config): DISCLAIMER = """\ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # DO NOT EDIT. AUTOMATICALLY GENERATED. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # Direct modifications to this file WILL be lost upon subsequent # regeneration of this configuration file. # # To have your modifications retained, you should use CLI command # imunify360-agent backup-systems # or activate/deactivate appropriate feature in UI. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # """ def __init__( self, *, path=os.path.join( "/etc/sysconfig/imunify360", Core.BACKUP_CONFIGFILENAME ), validation_schema=CONFIG_SCHEMA_BACKUP_SYSTEM, ): super().__init__(path=path, validation_schema=validation_schema) class BackupRestore: ENABLED = FromConfig( section="BACKUP_SYSTEM", option="enabled", config_cls=BackupConfig ) _BACKUP_SYSTEM = FromConfig( section="BACKUP_SYSTEM", option="backup_system", config_cls=BackupConfig, ) CL_BACKUP_ALLOWED = FromConfig( section="BACKUP_RESTORE", option="cl_backup_allowed", ) CL_ON_PREMISE_BACKUP_ALLOWED = FromConfig( section="BACKUP_RESTORE", option="cl_on_premise_backup_allowed", ) @classmethod def backup_system(cls): return _get_backend_system(cls._BACKUP_SYSTEM) def _get_backend_system(name): """ Get backup module from its name :param name: backup system name :return: backup system module """ from restore_infected import backup_backends if name in (CLOUDLINUX, CLOUDLINUX_ON_PREMISE): # cloudlinux backup is actually acronis one name = ACRONIS elif name is None: return None return backup_backends.backend(name, async_=True) class AcronisBackup: # https://kb.acronis.com/content/1711 # https://kb.acronis.com/content/47189 LOG_NAME = "acronis-installer.log" PORTS = (8443, 44445, 55556) RANGE = {(7770, 7800)} def should_try_autorestore_malicious(username: str) -> bool: """ Checks is Agent should try restore malware file firts and returns user that set this action in config """ try_restore, _ = choose_value_from_config( "MALWARE_SCANNING", "try_restore_from_backup_first", username ) return BackupRestore.ENABLED and try_restore def choose_use_backups_start_from_date(username: str) -> datetime: max_days, _ = choose_value_from_config( "BACKUP_RESTORE", "max_days_in_backup", username ) until = datetime.now() - timedelta(days=max_days) return until def should_send_user_notifications(username: str) -> bool: should_send, _ = choose_value_from_config( "CONTROL_PANEL", "generic_user_notifications", username=username, ) return should_send class Wordpress: SECURITY_PLUGIN_ENABLED = FromConfig( "WORDPRESS", "security_plugin_enabled" ) WAF_ENABLED = FromConfig("WORDPRESS", "waf_enabled") WAF_DEFAULT = FromConfig("WORDPRESS", "waf_default") AI_BOT_PROTECTION = FromConfig("WORDPRESS", "ai_bot_protection") AI_BOT_PROTECTION_PRESET = FromConfig( "WORDPRESS", "ai_bot_protection_preset" ) class HackerTrap: DIR = "/var/imunify360" NAME = "malware_found_b64.list" SA_NAME = "malware_standalone_b64.list" DIR_PD = "/opt/imunify360/proactive/dangerlist/" defence360agent/contracts/config_provider.py0000644000000000000000000003336100000000000016305 0ustar import json import logging import os import pwd from abc import abstractmethod from contextlib import suppress from textwrap import dedent from typing import Mapping, Optional, Protocol import sentry_sdk import yaml from defence360agent.utils import atomic_rewrite from defence360agent.utils.fd_ops import open_dir_no_symlinks logger = logging.getLogger(__name__) # Don't read config if its file is larger than this. _MAX_CONFIG_SIZE = 1 << 20 # 1MiB class IConfigProvider(Protocol): @abstractmethod def read_config_file( self, force_read: bool = False, ignore_errors: bool = True ): raise NotImplementedError @abstractmethod def write_config_file(self, config: Mapping) -> None: raise NotImplementedError @abstractmethod def modified_since(self, timestamp: Optional[float]) -> bool: raise NotImplementedError class ConfigError(Exception): pass class JsonMessage: """Pretty-print given *obj* as JSON. To be used for logging. Example: logging.info("object: %s", JsonMessage(obj)) """ def __init__(self, obj): self._obj = obj def __str__(self): return json.dumps(self._obj, sort_keys=True) def diff_section(prev_section: Optional[dict], section: Optional[dict]): """Return difference between config sections.""" prev_section = prev_section or {} section = section or {} removed_settings = prev_section.keys() - section.keys() added_settings = section.keys() - prev_section.keys() return { "-": {v: prev_section[v] for v in removed_settings}, "+": {v: section[v] for v in added_settings}, # modified settings "?": { v: (prev_section[v], section[v]) for v in (prev_section.keys() & section.keys()) if prev_section[v] != section[v] }, } def diff_config(prev_conf: dict, conf: dict): """Compare *prev_conf* with the current *conf*.""" removed_sections = prev_conf.keys() - conf.keys() yield {section: prev_conf[section] for section in removed_sections} added_sections = conf.keys() - prev_conf.keys() yield {section: conf[section] for section in added_sections} # changed sections yield { section: diff_section(prev_conf[section], conf[section]) for section in (prev_conf.keys() & conf.keys()) if prev_conf[section] != conf[section] } def exclude_equals(*, main_conf: dict, base_conf: dict) -> dict: """ Return dict derived from *main_conf* excluding parts that are equal in *base_conf*. For example, >>> base_conf = { "SECTION1": {"OPTION1": "default", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> main_conf = { "SECTION1": {"OPTION1": "value", "OPTION2": "default"}, "SECTION2": {"OPTION1": "default"} } >>> >>> exclude_equals(main_conf=main_conf, base_conf=base_conf) {'SECTION1': {'OPTION1': 'value'}} >>> """ _, added, changed = diff_config(base_conf, main_conf) result = {} for section, value in main_conf.items(): if section in added.keys(): result[section] = value if section in changed.keys(): result.setdefault(section, {}).update(changed[section]["+"]) result.setdefault(section, {}).update( {k: v[1] for k, v in changed[section]["?"].items()} ) return result class ConfigReader: """ ConfigFile file for settings page. Location config file is PATH """ def __init__(self, path, disclaimer="", permissions=None): self.path = path self.disclaimer = disclaimer self.permissions = permissions def __repr__(self): return "<{classname}({path})>".format( classname=self.__class__.__qualname__, path=self.path ) def __str__(self): return f"ConfigReader at {self.path}" def read_config_file( self, force_read: bool = False, ignore_errors: bool = True ) -> dict: """Read config file into memory. Raises ConfigError. """ try: if os.path.getsize(self.path) > _MAX_CONFIG_SIZE: raise ConfigError("Config file is too large") filename = self.path with open(filename, "r") as config_file: logger.info("Reading config file %s", filename) text = config_file.read() except UnicodeDecodeError as e: raise ConfigError("Unable to decode config file") from e except FileNotFoundError: return {} try: return self.load_config_body(text) except ConfigError as e: logger.error(e) if ignore_errors: return {} raise e def load_config_body(self, text: str) -> dict: try: config = yaml.safe_load(text) except yaml.YAMLError as e: raise ConfigError( f"Imunify360 config is not valid YAML document ({e})" ) from e if config is None: return {} if not isinstance(config, dict): raise ConfigError( "Imunify360 config is invalid or empty" ": path={!r}, text={!r}".format(self.path, text) ) return config def _pre_write(self): pass def _post_write(self): pass def _serialize_config(self, config) -> str: config_text = "" if self.disclaimer: config_text += dedent(self.disclaimer) config_text += "\n" config_text += yaml.dump(config, default_flow_style=False) return config_text def write_config_file(self, config) -> str: self._pre_write() config_text = self._serialize_config(config) atomic_rewrite( self.path, config_text, backup=False, permissions=self.permissions ) self._post_write() return config_text def modified_since(self, timestamp: Optional[float]) -> bool: return True class CachedConfigReader(ConfigReader): def __init__(self, path, disclaimer="", permissions=None): super().__init__(path, disclaimer) self.mtime: Optional[float] = None self.size: Optional[float] = None self._config = {} self.permissions = permissions def __str__(self): return ( "{classname} <'{path}', modified at {mtime}, {size} bytes>".format( classname=self.__class__.__qualname__, path=self.path, mtime=self.mtime, size=self.size, ) ) def read_config_file( self, force_read: bool = False, ignore_errors: bool = True ): """Update config if config file is modified""" if self.modified_since(self.mtime) or force_read: prev_config = self._config try: self._config = super().read_config_file( ignore_errors=ignore_errors ) except ConfigError as error: sentry_sdk.capture_exception(error) logger.warning( "%s is invalid, using previous settings: %s", self, JsonMessage(self._config), ) if not ignore_errors: raise error else: if self.mtime is not None: # don't log on startup diffs = list(diff_config(prev_config, self._config)) if any(diffs): # content has changed, log it logger.info( "%s modified: removed=%s, added=%s, changed=%s", self, *map(JsonMessage, diffs), ) self._refresh_stat_cache() return self._config def _refresh_stat_cache(self) -> None: """Sync cached mtime/size with the file on disk.""" try: stat = os.stat(self.path) self.mtime = stat.st_mtime self.size = stat.st_size except FileNotFoundError: self.mtime = 0.0 self.size = 0.0 def modified_since(self, timestamp: Optional[float]) -> bool: """Whether the config has updated since *timestamp*. (as defined by its last modification time and size) :param timestamp: None means that the file has never been read before """ # On startup consider timestamp to be None if timestamp is None: timestamp = 0.0 try: stat = os.stat(self.path) except FileNotFoundError: st_mtime, st_size = 0.0, 0.0 else: st_mtime, st_size = stat.st_mtime, stat.st_size return st_mtime > timestamp or st_size != self.size class WriteOnlyConfigReader(CachedConfigReader): def __init__(self, path, disclaimer="", permissions=None): super().__init__(path, disclaimer, permissions) # write-only readers never hit the parent read path that populates # mtime/size, so seed them from disk now — otherwise the size # fallback in modified_since() (st_size != self.size) compares # against None forever and the check is stuck at True. self._refresh_stat_cache() def read_config_file(self, *_, **__): return self._config def write_config_file(self, config): config_text = super().write_config_file(config) self._config = self.load_config_body(config_text) self._refresh_stat_cache() return config_text class UserConfigReader(CachedConfigReader): """Per-user config reader that resists TOCTOU symlink attacks. The user-specific subdirectory ``//`` and the config file inside it must end up owned by ``root:`` with modes ``0750`` / ``0640``. Earlier revisions performed the ``mkdir`` -> ``chown`` -> ``chmod`` sequence on path strings, which left a TOCTOU window: between the directory existing and the metadata syscalls, a swap to a symlink could redirect the chown to an arbitrary inode. See DEF-41586 / CLOS-3965 for context. The hardened path opens the parent ``USER_CONFDIR`` once with ``O_NOFOLLOW`` at every component, then performs every subsequent operation (``mkdir``/``chown``/``chmod``/atomic write) relative to that fd or to a fresh ``O_NOFOLLOW`` fd of the user subdir. No user-controlled path string is dereferenced more than once. """ DIR_PERMISSIONS = 0o750 FILE_PERMISSIONS = 0o640 def __init__(self, path, username): super().__init__(path) self.username = username def __str__(self): return f"Config of user {self.username}" def _open_user_subdir(self, parent_fd: int, name: str) -> int: """Return an O_NOFOLLOW fd for ``name`` inside *parent_fd*. Creates the directory first if it does not already exist. The ``O_NOFOLLOW`` flag guarantees that, if a symlink appears in the slot at any time after this call returns, every subsequent ``fchown``/``fchmod``/atomic-rewrite bound to the returned fd operates on the originally opened inode. """ with suppress(FileExistsError): os.mkdir(name, mode=self.DIR_PERMISSIONS, dir_fd=parent_fd) return os.open( name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=parent_fd, ) def write_config_file(self, config) -> str: gid = pwd.getpwnam(self.username).pw_gid confdir, basename = os.path.split(self.path) userconfdir, username = os.path.split(confdir) # Open USER_CONFDIR (root-owned, package-controlled) with full # symlink protection at every path component. Then descend to # the per-user subdir using a dir_fd-relative open with # O_NOFOLLOW so a symlink swap cannot redirect us. parent_fd = open_dir_no_symlinks(userconfdir) try: user_fd = self._open_user_subdir(parent_fd, username) try: # Apply directory ownership/permissions on the fd we # just opened — bound to the inode, not to the path. os.chown(user_fd, 0, gid) os.fchmod(user_fd, self.DIR_PERMISSIONS) config_text = self._serialize_config(config) # atomic_rewrite_fd creates a temp file via # O_CREAT|O_EXCL|O_NOFOLLOW relative to user_fd, chowns # and chmods the temp inode (not a path), then renames # it into place — all without leaving a TOCTOU window. atomic_rewrite( basename, config_text, backup=False, uid=0, gid=gid, permissions=self.FILE_PERMISSIONS, dir_fd=user_fd, ) # Re-normalize ownership/permissions on every call so # that an out-of-band ``chmod``/``chown`` between writes # cannot leave the file with weaker permissions. When # ``atomic_rewrite_fd`` short-circuits on identical # content, no chown/chmod runs there, so we apply them # here. ``O_NOFOLLOW`` keeps the fix TOCTOU-safe. file_fd = os.open( basename, os.O_RDONLY | os.O_NOFOLLOW, dir_fd=user_fd, ) try: os.chown(file_fd, 0, gid) os.fchmod(file_fd, self.FILE_PERMISSIONS) finally: os.close(file_fd) finally: os.close(user_fd) finally: os.close(parent_fd) return config_text defence360agent/contracts/eula.py0000644000000000000000000000274200000000000014053 0ustar import asyncio import os.path from typing import Optional from defence360agent import files from defence360agent.contracts.config import ANTIVIRUS_MODE from defence360agent.model.simplification import Eula, run_in_executor _MESSAGE_TEMPLATE = "message{}.txt" _SUFFIX = "-av" if ANTIVIRUS_MODE else "" _TEXT_TEMPLATE = "eula{}.txt" _UPDATED_TEMPLATE = "updated{}.txt" def _readfile(path: str, errors: Optional[str] = None) -> str: with open(path, errors=errors) as f: return f.read().strip() def _get_path(template: str) -> str: return os.path.join( files.Index.files_path(files.EULA), template.format(_SUFFIX) ) async def is_accepted() -> bool: """Return True if latest EULA was accepted, False otherwise.""" return await run_in_executor(asyncio.get_event_loop(), Eula.is_accepted) async def accept() -> None: """Accepts EULA.""" await run_in_executor(asyncio.get_event_loop(), Eula.accept) async def update() -> None: """Updates latest EULA date from files.""" await run_in_executor( asyncio.get_event_loop(), lambda: Eula.get_or_create(updated=updated()) ) def text() -> str: """Return main text of the EULA.""" return _readfile(_get_path(_TEXT_TEMPLATE), errors="ignore") def message() -> str: """Return a message inviting to accept EULA.""" return _readfile(_get_path(_MESSAGE_TEMPLATE)) def updated() -> str: """Return last EULA's update time.""" return _readfile(_get_path(_UPDATED_TEMPLATE)) defence360agent/contracts/hook_events.py0000644000000000000000000000301400000000000015442 0ustar # todo: figure out how HookEvents.* is typed # type: ignore from defence360agent.contracts.config import HookEvents from defence360agent.contracts.messages import Message STARTED, FINISHED = "started", "finished" class _HookEventBase(Message): event = None subtype = None def __repr__(self): filtered = {k: v for k, v in self.items() if k != "DUMP"} return f"{self.__class__.__qualname__}({repr(filtered)})" class _Agent(_HookEventBase): event = HookEvents.AGENT class _License(_HookEventBase): event = HookEvents.LICENSE class _MalwareScanning(_HookEventBase): event = HookEvents.MALWARE_SCANNING class _MalwareDetected(_HookEventBase): event = HookEvents.MALWARE_DETECTED class _MalwareCleanup(_HookEventBase): event = HookEvents.MALWARE_CLEANUP class HookEvent: class AgentStarted(_Agent): subtype = STARTED class AgentMisconfig(_Agent): subtype = "misconfig" class LicenseExpired(_License): subtype = "expired" class LicenseExpiring(_License): subtype = "expiring" class LicenseRenewed(_License): subtype = "renewed" class MalwareScanningStarted(_MalwareScanning): subtype = STARTED class MalwareScanningFinished(_MalwareScanning): subtype = FINISHED class MalwareDetectedCritical(_MalwareDetected): subtype = "critical" class MalwareCleanupStarted(_MalwareCleanup): subtype = STARTED class MalwareCleanupFinished(_MalwareCleanup): subtype = FINISHED defence360agent/contracts/hooks.py0000644000000000000000000001420600000000000014246 0ustar import grp import os from defence360agent.contracts.config import Config, Core from defence360agent.contracts.config_provider import ConfigReader from defence360agent.utils import antivirus_mode class Schema: @staticmethod def dict(data): return { "type": "dict", "schema": data, "default": {}, } @staticmethod def list_of_strings(regex=None): return { "type": "list", "schema": { "type": "string", **({"regex": regex} if regex else {}), }, "nullable": False, "default": [], } @staticmethod def list_of_emails(default_enabled=True): regex = ( r"^.+@(.+\.)+.+|default$" if default_enabled else r"^.+@(.+\.)+.+$" ) return Schema.list_of_strings(regex) @staticmethod def period(): return { "period": { "type": "integer", "coerce": int, "min": 1, "default": 1, } } @staticmethod def string(nullable): return { "type": "string", "nullable": nullable, } @staticmethod def enabled(): return { "enabled": { "type": "boolean", "default": False, } } @staticmethod def admin(period): return { "ADMIN": Schema.dict( { **Schema.enabled(), "admin_emails": Schema.list_of_emails(), **(Schema.period() if period else {}), } ) } @staticmethod def script(period): return { "SCRIPT": Schema.dict( { **Schema.enabled(), "scripts": Schema.list_of_strings(r"^\/.+$"), **(Schema.period() if period else {}), } ) } @staticmethod def user(period): return { "USER": Schema.dict( { **Schema.enabled(), **(Schema.period() if period else {}), } ) } @staticmethod def target_script(period=False): return Schema.dict( { **Schema.script(period=period), } ) @staticmethod def target_admin_and_script(period=False): return Schema.dict( { **Schema.admin(period=period), **Schema.script(period=period), } ) @staticmethod def target_all(period=False): return Schema.dict( { **Schema.admin(period=period), # **Schema.user(period=period), # stage 2 **Schema.script(period=period), } ) class HooksConfigReader(ConfigReader): GROUP_NAME = "_imunify" def _post_write(self): os.chmod(self.path, 0o640) os.chown(self.path, 0, grp.getgrnam(self.GROUP_NAME).gr_gid) class HooksConfig(Config): def __init__( self, path=os.path.join(Core.GLOBAL_CONFDIR, Core.HOOKS_CONFIGFILENAME) ): validation_schema = ( { "admin": Schema.dict( { "default_emails": Schema.list_of_emails( default_enabled=False ), "notify_from_email": { "type": "string", "default": None, "nullable": True, }, "locale": Schema.string(nullable=True), } ), "users": { "type": "list", "schema": Schema.dict( { "username": Schema.string(nullable=False), "emails": Schema.list_of_emails(), "locale": Schema.string(nullable=True), } ), "nullable": True, "default": [], }, "rules": Schema.dict( { "REALTIME_MALWARE_FOUND": ( Schema.target_admin_and_script(period=True) ), "USER_SCAN_MALWARE_FOUND": Schema.target_all(), "SCRIPT_BLOCKED": Schema.target_admin_and_script( period=True ), "USER_SCAN_STARTED": Schema.target_script(), "CUSTOM_SCAN_STARTED": Schema.target_script(), "USER_SCAN_FINISHED": Schema.target_script(), "CUSTOM_SCAN_FINISHED": Schema.target_script(), "CUSTOM_SCAN_MALWARE_FOUND": ( Schema.target_admin_and_script() ), } ), "default": {}, } if antivirus_mode.disabled else { "rules": Schema.dict( { "USER_SCAN_MALWARE_FOUND": Schema.target_script(), "USER_SCAN_STARTED": Schema.target_script(), "CUSTOM_SCAN_STARTED": Schema.target_script(), "USER_SCAN_FINISHED": Schema.target_script(), "CUSTOM_SCAN_FINISHED": Schema.target_script(), "CUSTOM_SCAN_MALWARE_FOUND": Schema.target_script(), } ), "default": {}, } ) super().__init__( path=path, validation_schema=validation_schema, config_reader=HooksConfigReader(path), ) def get(self): data = self.config_to_dict() data.pop("users", None) return data def update(self, data): data.pop("users", None) self.dict_to_config(data) defence360agent/contracts/license.py0000644000000000000000000006150300000000000014547 0ustar import asyncio import base64 import binascii import datetime import json import os import shutil import subprocess import tempfile import time from contextlib import suppress from json import JSONDecodeError from pathlib import Path from subprocess import TimeoutExpired from typing import Optional from peewee import OperationalError from defence360agent.application.determine_hosting_panel import ( is_cpanel_installed, ) from defence360agent.contracts import sentry from defence360agent.contracts.config import ( ANTIVIRUS_MODE, Core, CustomBilling, int_from_envvar, logger, ) from defence360agent.contracts.hook_events import HookEvent from defence360agent.internals.global_scope import g from defence360agent.subsys.panels.plesk.upgrade_urls import ( get_plesk_upgrade_urls, ) from defence360agent.utils import retry_on, timed_cache from defence360agent.utils.common import HOUR, rate_limit from defence360agent.utils.ipecho import APIError, IPEchoAPI from defence360agent.utils.validate import IP AV_DEFAULT_ID = "IMUNIFYAV" UNLIMITED_USERS_COUNT = 2147483647 # no need to check the license file more often than # once every 10 minutes, this should be enough to fix DEF-14677 _CACHE_LICENSE_TOKEN_TIMEOUT = int_from_envvar( "IMUNIFY360_CACHE_LICENSE_TOKEN_TIMEOUT", 10 * 60, # in seconds ) # path to openssl binary used to check license signature # we need to check several paths because of different OSes # and different installation paths with fallback to system default if not (OPENSSL_BIN := Path("/opt/alt/openssl11/bin/openssl")).exists(): if not (OPENSSL_BIN := Path("/opt/alt/openssl/bin/openssl")).exists(): OPENSSL_BIN = Path("/usr/bin/openssl") throttled_log_error = rate_limit(period=HOUR, on_drop=logger.warning)( logger.error ) throttled_log_no_v2 = rate_limit(period=HOUR, on_drop=logger.warning)( logger.error ) class LicenseError(Exception): """Used to communicate that some function requires a license""" class LicenseCLN: VERIFY_FIELDS_V1 = ( "id", "status", "group", "limit", "token_created_utc", "token_expire_utc", ) VERIFY_FIELDS_V2 = ( "id", "status", "limit", "token_created_utc", "token_expire_utc", "group_id", "permissions", ) VERIFY_FIELDS_MAP = { 1: VERIFY_FIELDS_V1, 2: VERIFY_FIELDS_V2, } _PUBKEY_FILE = "/usr/share/imunify360/cln-pub.key" _ALTERNATIVE_PUBKEY_FILES = ( # keys for self-signed licenses "/usr/share/imunify360/alt-license-pub.key", ) _LICENSE_FILE = "/var/imunify360/license.json" _FREE_LICENSE_FILE = "/var/imunify360/license-free.json" AV_PLUS_BUY_URL = ( "https://cln.cloudlinux.com/console/purchase/ImunifyAvPlus" ) IM360_BUY_URL_TEMPLATE = ( "https://www.cloudlinux.com/upgrade-imunify-{user_count}/" ) CPANEL_UPGRADE_URL = ( "../../../scripts14/purchase_imunifyavplus_init_IMUNIFY" ) CPANEL_UPGRADE_URL_360 = ( "../../../scripts14/purchase_imunify360_init_IMUNIFY" ) VERSION_THRESHOLDS = [1, 30, 250] _token = {} users_count = None @staticmethod @retry_on(TimeoutExpired, max_tries=2) def _verify_signature( pubkey_path: str, content: bytes, signature: bytes ) -> tuple[bool, Optional[list[str]]]: """Verify that `content` is correctly signed with public key from file `pubkey_path` with resulting `signature`. Returns a tuple with (success, error_list). """ errors: list[str] = [] result = False with tempfile.NamedTemporaryFile(delete=True) as sig_file: sig_file.write(signature) sig_file.flush() cmd = [ OPENSSL_BIN, "dgst", "-sha512", "-verify", pubkey_path, "-signature", sig_file.name, ] try: p = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=content, timeout=5, ) except FileNotFoundError as e: errors.append(f"openssl command failed: missing {e.filename}") else: if p.returncode == 0: result = True else: errors.append( "Signature verification failed - " f"openssl returned {p.returncode}. " f"stdout: {p.stdout}, stderr: {p.stderr}" ) return result, errors or None @classmethod def _get_signature_input(cls, license, version: int = 1) -> bytes: parts = [] for key in cls.VERIFY_FIELDS_MAP[version]: value = license[key] if isinstance(value, dict): parts.append( "".join( f"{subkey}={subvalue}" for subkey, subvalue in value.items() ) ) elif value is None: parts.append("null") else: parts.append(str(value)) return "".join(parts).encode() @classmethod def _find_signature( cls, license_token, signature_list: list[tuple[str, int]] ) -> tuple[Optional[str], bool]: """ Verify signatures in license :return: signature, is_alternative, version """ sign: str all_errors: list[str] = [] def verify_and_collect_errors(*args, **kwargs): success, errors = cls._verify_signature(*args, **kwargs) if errors: all_errors.extend(errors) return success for sign, version in signature_list: signature = base64.b64decode(sign) try: content = cls._get_signature_input( license_token, version=version ) except KeyError: continue if verify_and_collect_errors(cls._PUBKEY_FILE, content, signature): return sign, False for alt_pubkey in cls._ALTERNATIVE_PUBKEY_FILES: if verify_and_collect_errors(alt_pubkey, content, signature): return sign, True for error in all_errors: logger.warning("%s", error) return None, False @classmethod def _load_token(cls, path): """ Load license token from file and verify signature If signature verification successful, put first valid signature to 'sign' field of license token :return: license token """ default = {} # default value returned on error try: with open(path) as f: license_token = json.load(f) if not isinstance(license_token, dict): logger.error( "Failed to load license. Expected JSON object, got %r" % (license_token,) ) return default signature, is_alternative = cls._find_signature( license_token, [ (sign, 1) for sign in license_token.get("signatures", []) ], ) v2_sign = license_token.get("signature_v2") if v2_sign: _sign, _ = cls._find_signature( license_token, [(v2_sign, 2)] ) if _sign is None: throttled_log_error( "Failed to verify license signature v2" ) license_token.pop("permissions", None) elif "permissions" in license_token: license_token.pop("permissions") throttled_log_no_v2( "License missing signature_v2 but contained " "permissions; stripped (possible tampering or " "stale token)" ) if signature is None: throttled_log_error("Failed to verify license signature") return default license_token["sign"] = signature license_token["is_alternative"] = is_alternative return license_token except FileNotFoundError: # this is a common case logger.info("Failed to load license: not registered?") except JSONDecodeError as e: # Likely a TOCTOU read of the file mid-write by the updater; # the next read cycle will pick up the fully-written content. logger.warning("Failed to load license: %s", e) except ( OSError, KeyError, UnicodeDecodeError, binascii.Error, TypeError, ) as e: # not loading broken license logger.error("Failed to load license: %s", e) return default @classmethod @timed_cache( datetime.timedelta(seconds=_CACHE_LICENSE_TOKEN_TIMEOUT), maxsize=1 ) def get_token(cls) -> dict: """ Get available license. In Antivirus mode, if main license is unavailable, return free license :return: license token """ lic_token = {} license_files = ( [cls._LICENSE_FILE, cls._FREE_LICENSE_FILE] if ANTIVIRUS_MODE else [cls._LICENSE_FILE] ) for lf in license_files: lic_token = cls._load_token(lf) if lic_token: return lic_token return lic_token @classmethod def get_server_id(cls) -> Optional[str]: """ :return: server id """ return cls.get_token().get("id") @classmethod def is_registered(cls): """ :return: bool: if we have token """ return bool(cls.get_token()) @classmethod def is_valid_av_plus(cls): """ :return: Return true only if we have valid ImunifyAV+ or Imunify360 license """ return ANTIVIRUS_MODE and cls.is_valid() and (not cls.is_free()) @classmethod def is_free(cls): if not ANTIVIRUS_MODE: return False return cls.get_server_id() == AV_DEFAULT_ID @classmethod def is_cloud_assisted_cleanup_allowed(cls) -> bool: """Cloud-assisted cleanup is a paid feature (Imunify360 / ImunifyAV+).""" if not ANTIVIRUS_MODE: return True return cls.is_valid_av_plus() @classmethod def is_valid(cls, token=None): """License check based on license token return True - if license token is valid for this server return False - if license token is invalid """ token = token or cls.get_token() if not token: return False if ANTIVIRUS_MODE: return ( token.get("status", "").startswith("ok") and token["token_expire_utc"] >= time.time() ) return ( token["status"] in ("ok", "ok-trial") and token["token_expire_utc"] >= time.time() and (cls.users_count is None or cls.users_count <= token["limit"]) ) @classmethod def has_permission(cls, permission: str, token=None): """License check for a specific permission based on a license token return True - if license token has a given permission for this server return False - if license token does not have permission """ token = token or cls.get_token() if not token: return False return ( permission in (perm := token.get("permissions", {})) and perm[permission] == "ENABLED" ) @classmethod def update(cls, token): """ Write new license token to file :param token: new token :return: """ old_token = cls.get_token() # Save user_limit under different name only during registration # Check if this is initial registration by checking if old token exists if not old_token and token.get("limit") is not None: token["saved_user_limit"] = token["limit"] temp_file = cls._LICENSE_FILE + ".tmp" flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL mode = 0o640 with suppress(FileNotFoundError): os.unlink(temp_file) with os.fdopen(os.open(temp_file, flags, mode), "w") as f: json.dump(token, f) shutil.chown(temp_file, user="root", group="_imunify") os.rename(temp_file, cls._LICENSE_FILE) cls.get_token.cache_clear() sentry.set_server_id(cls.get_server_id()) sentry.set_product_name(cls.get_product_name()) try: cls.renew_hook(old_token, token) except OperationalError: pass @classmethod def renew_hook(cls, old_token, token): important_keys = ["license_expire_utc", "status", "limit", "id"] exp_time = token.get("license_expire_utc") license_type = cls.fill_license_type(token) condition = any( [token.get(elem) != old_token.get(elem) for elem in important_keys] ) if condition: license_updated = HookEvent.LicenseRenewed( exp_time=exp_time, license=license_type ) from defence360agent.hooks.execute import execute_hooks asyncio.gather( execute_hooks(license_updated), return_exceptions=True ) @classmethod def delete(cls): """ Delete license token along with old-style license data :return: """ with suppress(FileNotFoundError): os.unlink(cls._LICENSE_FILE) cls.get_token.cache_clear() sentry.set_server_id(None) sentry.set_product_name(cls.get_product_name()) @classmethod def fill_license_type(cls, token): license_type = token.get("status") license_type_to_product = { "ok": "imunify360", "ok-trial": "imunify360Trial", "ok-av": "imunifyAV", "ok-avp": "imunifyAVPlus", } return license_type_to_product.get(license_type) @classmethod def get_license_type(cls): return cls.fill_license_type(cls.get_token()) @classmethod def is_ip_license_type(cls): token = cls.get_token() if token.get("id", "").lower().startswith("ip-"): return True return False @classmethod def format_upgrade_url(cls, url_template: Optional[str]) -> Optional[str]: """Format upgrade URL template with available parameters. Args: url_template: URL template string that may contain {user_count}, {iaid}, and {users} placeholders Returns: Formatted URL with placeholders replaced with actual values """ if not url_template: return url_template n = cls.users_count iaid = g.get("iaid", "") # Determine user_count value if n is None: user_count = 1 else: for threshold in cls.VERSION_THRESHOLDS: if n <= threshold: user_count = threshold break else: user_count = "unlimited" url_template = url_template.replace("{user_count}", str(user_count)) url_template = url_template.replace("{iaid}", iaid) url_template = url_template.replace( "{users}", str(n if n is not None else 1) ) return url_template @classmethod def _get_license_tier_recommendation(cls, user_count): """Get recommended license tier based on user count.""" if user_count == 1: return "Single user" elif user_count <= 30: return "Up to 30 users" elif user_count <= 250: return "Up to 250 users" else: return "Unlimited users" @classmethod def _format_license_exceeded_message(cls, user_count): """Format enhanced message when user count exceeds saved limit.""" if user_count is None: return ( "WARNING: License is invalid for current server. " "Unable to determine user count; please check KB article: " "https://cloudlinux.zendesk.com/hc/en-us/articles/" ) tier_name = cls._get_license_tier_recommendation(user_count) user_word = "user" if user_count == 1 else "users" return ( "WARNING: License is invalid for current server. " f"Detected {user_count} {user_word} → " f'purchase the "{tier_name}" Imunify360 license. ' "Pricing: https://imunify360.com/pricing" ) @classmethod def license_info(cls): token = cls.get_token() key_360 = token.get("status") in ("ok", "ok-trial") message = token.get("message", None) if ( ANTIVIRUS_MODE and CustomBilling.UPGRADE_URL and not CustomBilling.NOTIFICATIONS ): message = None if ANTIVIRUS_MODE and key_360 and not message: # TODO: remove after auto-upgrade will be implemented message = ( "You've got a license for the advanced security product " "Imunify360. Please, uninstall ImunifyAV and replace it with " "the Imunify360 providing comprehensive security for your " "server. Here are the steps for upgrade: " "https://docs.imunify360.com/installation/" ) if token: info = { "status": cls.is_valid(), "expiration": token.get("license_expire_utc", 0), "user_limit": token.get("limit"), "id": token.get("id"), "user_count": cls.users_count, "message": message, "license_type": cls.fill_license_type(token), } # Generate enhanced message if license is invalid due to user limit exceeded # Compare against saved_user_limit instead of current limit if ( not ANTIVIRUS_MODE and token.get("saved_user_limit") is not None and cls.users_count is not None and cls.users_count > token.get("saved_user_limit") ): info["message"] = cls._format_license_exceeded_message( cls.users_count ) else: info = {"status": False} info["upgrade_url"] = None info["upgrade_url_360"] = None if ANTIVIRUS_MODE: ignored_messages = [ "user limits", ] if info.get("message"): for msg in ignored_messages: if msg in info["message"]: info["message"] = None # Only add ip_license when we have a valid token, since the schema # for status=false doesn't allow this property (additionalProperties: false) if token: info["ip_license"] = CustomBilling.IP_LICENSE and ( CustomBilling.UPGRADE_URL is not None or CustomBilling.UPGRADE_URL_360 is not None ) plesk_urls = get_plesk_upgrade_urls() info["upgrade_url"] = ( cls.format_upgrade_url(CustomBilling.UPGRADE_URL) or plesk_urls["buy_url"] or token.get("upgrade_url") or cls.AV_PLUS_BUY_URL ) info["upgrade_url_360"] = ( cls.format_upgrade_url(CustomBilling.UPGRADE_URL_360) or plesk_urls["upgrade_license_url"] or plesk_urls["buy_url"] or upgrade_url_default() ) # redirect_url is required for the no-license schema (status=false) # Set it to None when there's no token, and from token otherwise if not token: info["redirect_url"] = None elif not ANTIVIRUS_MODE: info["redirect_url"] = token.get("upgrade_url", None) if cls.is_demo(): # pragma: no cover info["demo"] = True info[ "eligible_for_imunify_patch" ] = cls.is_eligible_for_imunify_patch() return info @classmethod def is_vps(cls) -> bool: return cls.users_count is not None and cls.users_count <= 1 @classmethod def is_custom_reseller_configured(cls) -> bool: upgrade_urls: list[Optional[str]] = [None] upgrade_urls_360: list[Optional[str]] = [None] if is_cpanel_installed(): upgrade_urls.append(cls.CPANEL_UPGRADE_URL) upgrade_urls_360.append(cls.CPANEL_UPGRADE_URL_360) # customer has any upgrade url other than default ones return not ( CustomBilling.UPGRADE_URL in upgrade_urls and CustomBilling.UPGRADE_URL_360 in upgrade_urls_360 ) @classmethod def is_eligible_for_imunify_patch(cls) -> bool: return ( cls.is_vps() and cls.is_free() and not cls.is_custom_reseller_configured() ) @classmethod def get_product_name(cls) -> str: if not ANTIVIRUS_MODE: return Core.NAME license_status = cls.get_token().get("status", "") if license_status == "ok-av": return "imunify.av" elif license_status in ("ok-avp", "ok", "ok-trial"): return "imunify.av+" else: logger.error("Unknown license %s", license_status) return "Unknown license" @classmethod def is_demo(cls) -> bool: return os.path.isfile("/var/imunify360/demo") @classmethod def is_unlimited(cls): token = cls.get_token() return token.get("limit", 0) >= UNLIMITED_USERS_COUNT @classmethod def get_im360_buy_url(cls) -> str: if cls.users_count is None: return cls.IM360_BUY_URL_TEMPLATE.format(user_count=1) for threshold in cls.VERSION_THRESHOLDS: if cls.users_count <= threshold: return cls.IM360_BUY_URL_TEMPLATE.format(user_count=threshold) return cls.IM360_BUY_URL_TEMPLATE.format(user_count="unlimited") def upgrade_url_default(): n = LicenseCLN.users_count iaid = g.get("iaid", "") if ( # apply custom direct store links on cPanel is_cpanel_installed() # where upgrade URL is not set or set to the old value and CustomBilling.UPGRADE_URL == LicenseCLN.CPANEL_UPGRADE_URL ): # We have a complex default value for cPanel installations configured # with that use cPanel as a reseller. They don't populate # the CUSTOM_BILLING config well, so we generate the links here. # (they care less about upsell than we do) if not _eligible_for_new_upgrade_links(iaid): # A/B experiment control (old) variant: # return the old URL that leads through cPanel login page return LicenseCLN.CPANEL_UPGRADE_URL_360 # A/B experiment test (new) variant: # return the new URL that leads directly to the store base_url = ( "https://store.cpanel.net/index.php?rp=/store/partner-addons/" ) server_ip = "" try: ip = IPEchoAPI.server_ip() # cPanel Store only accepts w4, not IPv6 # If we got IPv6, leave the IP field blank if IP.is_valid_ipv4_addr(ip): server_ip = ip else: logger.info( "Server IP is IPv6 (%s), cPanel Store requires IPv4. " "Omitting IP parameter.", ip, ) except APIError as e: logger.warning("Failed to get server IP: %s", e) if n == 1: suffix = ( f"imunify360-for-cpanel-solo&customfield%5B55%5D={server_ip}" ) else: suffix = f"imunify360&customfield%5B375%5D={server_ip}" return base_url + suffix return ( LicenseCLN.get_im360_buy_url() + f"?iaid={iaid}" + f"&users={n}" * bool(n) ) def _eligible_for_new_upgrade_links(iaid: str) -> bool: logger.debug("checking if iaid: %s is eligible for upgrade", iaid) if len(iaid) == 0: logger.warning("receive empty iaid, fallback to old link") return False try: hex_bucket = int(iaid[0], 16) except ValueError: logger.warning("iaid is not hex, fallback to old link") return False hex_mid = 8 # check if iaid falls under 50% of iaid distribution return hex_bucket < hex_mid defence360agent/contracts/messages.py0000644000000000000000000004240100000000000014730 0ustar import asyncio import json import os from enum import Enum from typing import List from defence360agent.contracts.config import Core as CoreConfig class MessageNotFoundError(Exception): pass class UnknownMessage: """ Used as stub for MessageType """ def __init__(self): raise MessageNotFoundError("Message class is not found.") def __getattr__(self, name): return "Unknown" # pragma: no cover class MessageT: _subclasses = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls._subclasses.append(cls) @classmethod def get_subclasses(cls): return tuple(cls._subclasses) class _MessageType: """ Used to get specific message class. For example, >>> _MessageType().ConfigUpdate >>> _MessageType().NotExistMessage >>> """ def __getattr__(self, name): for subcls in Message.get_subclasses(): # is is supposed that all subclasses have different names if subcls.__name__ == name: return subcls return UnknownMessage MessageType = _MessageType() class ReportTarget(Enum): API = "api" PERSISTENT_CONNECTION = "conn" class Reportable(MessageT): """ Mixin class for messages that should be sent to the server """ TARGET = ReportTarget.PERSISTENT_CONNECTION @classmethod def get_subclass_with_method(cls, method: str): """ Return a subclass with the same DEFAULT_METHOD as *method*. It can be used to detect report target from message method. NOTE: it is not guaranteed that the class with the *method* is unique, in this case the first subclass found is returned, but it is tested that all such subclasses have the same TARGET. """ for subclass in cls.__subclasses__(): if method == getattr(subclass, "DEFAULT_METHOD"): return subclass return None # pragma: no cover class Received(MessageT): """ Mixin class for messages received from the server. These messages are created in the client360 plugin when receiving a request from imunify360.cloudlinux.com. """ @classmethod def get_subclass_with_action(cls, action: str): for subclass in cls.__subclasses__(): received_actions = getattr(subclass, "RECEIVED_ACTIONS", []) or [ getattr(subclass, "DEFAULT_METHOD") ] if action in received_actions: return subclass raise MessageNotFoundError( 'Message class is not found for "{}" action'.format(action) ) class Lockable(MessageT): _lock = None @classmethod async def acquire(cls) -> None: if cls._lock is None: cls._lock = asyncio.Lock() await cls._lock.acquire() @classmethod def locked(cls) -> bool: return cls._lock is not None and cls._lock.locked() @classmethod def release(cls) -> None: if cls._lock is not None: cls._lock.release() class Message(dict, MessageT): """ Base class for messages to be passed as a parameter to plugins.MessageSink.process_message() """ # Default method='...' to send to the Server DEFAULT_METHOD = "" PRIORITY = 10 PROCESSING_TIME_THRESHOLD = 60 # 1 min #: fold collections' repr with more than the threshold number of items _FOLD_LIST_THRESHOLD = 100 #: shorten strings longer than the threshold characters _SHORTEN_STR_THRESHOLD = 320 def __init__(self, *args, **kwargs) -> None: if self.DEFAULT_METHOD: self["method"] = self.DEFAULT_METHOD super(Message, self).__init__(*args, **kwargs) @property def payload(self): return {k: v for k, v in self.items() if k != "method"} def __getattr__(self, name): """ Called when an attribute lookup has not found the attribute in the usual places A shortcut to access an item from dict """ try: return self[name] except KeyError as exc: raise AttributeError(name) from exc def __repr__(self): """Render for logs: collections with more than _FOLD_LIST_THRESHOLD items are collapsed to a count and strings longer than _SHORTEN_STR_THRESHOLD are shortened, recursively through nested payloads, so a single message cannot flood the log.""" folded_msg = { k: _fold_repr_value( v, fold_limit=self._FOLD_LIST_THRESHOLD, str_limit=self._SHORTEN_STR_THRESHOLD, ) for k, v in self.items() } return "{}({})".format(self.__class__.__qualname__, folded_msg) def __str__(self): return self.__repr__() class MessageList(Message): def __init__(self, msg_list): super().__init__(list=msg_list) @property def payload(self): return self.list class ShortenReprListMixin: """ Do not flood console.log with large sequences The method collapses messages that are a list. Instead of showing all the elements of the message, their number will be displayed. """ def __repr__(self: dict): # type: ignore return "{}({})".format( self.__class__.__qualname__, "<{} item(s)>".format(len(self.get("items", []))), ) class Accumulatable(Message): """Messages of this class will be grouped into a list of LIST_CLASS message instance by Accumulate plugin. Messages whose do_accumulate() call returns False will not be added to list.""" LIST_CLASS = MessageList def do_accumulate(self) -> bool: """Return True if this message is worth collecting, False otherwise.""" return True class ServerConnected(Message): pass # alias (for better client code readability) class ServerReconnected(ServerConnected): pass class Ping(Message, Reportable): """ Will send this message on connected, reconnected events to provide central server with agent version """ DEFAULT_METHOD = "PING" PRIORITY = 0 def __init__(self): super().__init__() self["version"] = CoreConfig.VERSION class Ack(Message, Reportable): """ Notify Server that a persistent message with *seq_number* has been received by Agent. """ DEFAULT_METHOD = "ACK" def __init__(self, seq_number, **kwargs): super().__init__(**kwargs) self["_meta"] = dict(per_seq=seq_number) class Noop(Message): """ Sending NOOP to the agent to track the message in agent logs. """ DEFAULT_METHOD = "NOOP" class ServerConfig(Message, Reportable): """ Information about server environment """ DEFAULT_METHOD = "SERVER_CONFIG" TARGET = ReportTarget.API def __repr__(self): return "{}()".format(self.__class__.__qualname__) class WpSecurityPluginStats(Message, Reportable): DEFAULT_METHOD = "WP_SECURITY_PLUGIN_STATS" TARGET = ReportTarget.API class DomainList(Message, Reportable): """ Information about server domains """ DEFAULT_METHOD = "DOMAIN_LIST" TARGET = ReportTarget.API def __repr__(self): return "{}()".format(self.__class__.__qualname__) class FilesUpdated(Message): """ To consume products of files.update() """ def __init__(self, files_type, files_index): """ :param files_type: files.Type :param files_index: files.LocalIndex """ # explicit is better than implicit self["files_type"] = files_type self["files_index"] = files_index def __repr__(self): """ Do not flood console.log with large sequences """ return "{}({{'files_type':'{}', 'files_index':{}}})".format( self.__class__.__qualname__, self["files_type"], self["files_index"], ) class UpdateFiles(Message, Received): """ Update files by getting message from the server """ DEFAULT_METHOD = "UPDATE" class ConfigUpdate(Message): DEFAULT_METHOD = "CONFIG_UPDATE" class Reject(Exception): """ Kinda message filtering facility. Raised in order to stop message processing through plugins. Takes reason of reject as argument. """ pass class Health(Message): DEFAULT_METHOD = "HEALTH" class CommandInvoke(Message, Reportable): DEFAULT_METHOD = "COMMAND_INVOKE" class ScanFailed(Message, Reportable): DEFAULT_METHOD = "SCAN_FAILED" class CleanupFailed(Message, Reportable): DEFAULT_METHOD = "CLEANUP_FAILED" class RestoreFromBackupTask(Message): """ Creates a task to restore files from backup """ DEFAULT_METHOD = "MALWARE_RESTORE_FROM_BACKUP" class cPanelEvent(Message): DEFAULT_METHOD = "PANEL_EVENT" ALLOWED_FIELDS = { "new_pkg", "plan", "exclude", "imunify360_proactive", "imunify360_av", } @classmethod def from_hook_event( cls, username: str, hook: str, ts: float, fields: dict ): data = { k.lower(): v for k, v in fields.items() if k.lower() in cls.ALLOWED_FIELDS } # Check for user rename if ( hook == "Modify" and "user" in fields and "newuser" in fields and fields["user"] != fields["newuser"] ): data["old_username"] = fields["user"] return cls( { "username": username, "hook": hook, "data": data, "timestamp": ts, } ) class IContactSent(Message, Reportable): DEFAULT_METHOD = "ICONTACT_SENT" def _shorten_str(s: str, limit: int) -> str: """Shorten *s* string if its length exceeds *limit*.""" assert limit > 4 return ( f"{s[: limit // 2 - 1]}...{s[-limit // 2 + 2 :]}" if len(s) > limit else s ) def _fold_repr_value(value, *, fold_limit: int, str_limit: int): if isinstance(value, str): return _shorten_str(value, str_limit) if isinstance(value, dict): if len(value) > fold_limit: return "<{} item(s)>".format(len(value)) return { k: _fold_repr_value(v, fold_limit=fold_limit, str_limit=str_limit) for k, v in value.items() } if isinstance(value, (list, tuple, set, frozenset)): if len(value) > fold_limit: return "<{} item(s)>".format(len(value)) return type(value)( _fold_repr_value(v, fold_limit=fold_limit, str_limit=str_limit) for v in value ) return value class BackupInfo(Message, Reportable): """Information about enabled backup backend""" DEFAULT_METHOD = "BACKUP_INFO" # Target serialized size per outgoing message chunk. Kept far below the # 10 MB NATS max_payload so envelope overhead and size-estimate drift cannot # push a chunk over the transport limit; the transport keeps a split-on- # overflow safety net for the rare cases this estimate misses. MAX_MESSAGE_SIZE = int( os.environ.get("IMUNIFY360_MAX_MESSAGE_SIZE", 1024 * 1024) ) def serialized_size(obj) -> int: from defence360agent.utils.json import ServerJSONEncoder try: return len(json.dumps(obj, cls=ServerJSONEncoder).encode()) except (TypeError, ValueError): return len(repr(obj).encode()) def estimate_size(obj) -> int: """Upper bound on obj's JSON byte size as sent on the wire (ensure_ascii), biased to never undercount. Far cheaper than a full ``serialized_size`` per call on big scans: JSON-native values are measured structurally without building the encoded string, and printable-ASCII strings (the common path for file paths/snippets) are counted with C-level ``str`` ops. Non-native values (peewee Models, IPs, ...) fall back to the exact ``serialized_size`` — their ``repr`` would wildly undercount the ServerJSONEncoder output. The transport keeps a split-on-overflow net for the rare drift this leaves.""" if obj is None: return 4 if isinstance(obj, bool): return 5 if isinstance(obj, int): return max(20, len(str(obj)) + 1) if isinstance(obj, float): return 24 if isinstance(obj, str): if obj.isascii() and obj.isprintable(): return len(obj) + 2 + obj.count('"') + obj.count("\\") return len(json.dumps(obj)) if isinstance(obj, (list, tuple)): return 2 + sum(estimate_size(v) + 1 for v in obj) if isinstance(obj, dict): return 2 + sum( estimate_size(k if isinstance(k, str) else str(k)) + 1 + estimate_size(v) + 1 for k, v in obj.items() ) return serialized_size(obj) class Splittable: """ A message list could be split into multiple batches. The split is possible for a list itself along with internal resources. """ LIST_SIZE = None BATCH_SIZE = None BATCH_FIELD = None @classmethod def _max_message_size(cls) -> int: return MAX_MESSAGE_SIZE @classmethod def _split_items(cls, messages: List[Accumulatable]): """ Split messages' internal lists of things into batches. A field that is meant to split is defined by `BATCH_FIELD`. """ if cls.BATCH_FIELD and cls.BATCH_SIZE: for message in messages: if (items := message.get(cls.BATCH_FIELD)) is None: yield message else: message_class = type(message) for batch in cls._size_bounded_batches(items, message): data = message.copy() data[cls.BATCH_FIELD] = batch new_message = message_class(data) yield new_message else: yield from iter(messages) @classmethod def _unit_size(cls, unit, is_dict: bool, message) -> int: """Serialized byte cost of one BATCH_FIELD unit. Subclasses override to also count data paired with the unit in sibling fields of the message (e.g. a per-hit cleanup result), so those bytes are not excluded from the byte budget.""" return estimate_size({unit[0]: unit[1]} if is_dict else unit) @classmethod def _message_size(cls, message) -> int: """Subclasses override when the list class drops part of the message before sending, so the budget counts only the bytes that go out.""" return estimate_size(message) @classmethod def _size_bounded_batches(cls, items, message): """Pack `items` into batches bounded by both the byte budget and the `BATCH_SIZE` count. A single element larger than the budget is emitted alone rather than dropped.""" budget = cls._max_message_size() is_dict = isinstance(items, dict) units = list(items.items()) if is_dict else items def build(buffer): return dict(buffer) if is_dict else list(buffer) buffer = [] size = 0 for unit in units: unit_size = cls._unit_size(unit, is_dict, message) if buffer and ( size + unit_size > budget or len(buffer) >= cls.BATCH_SIZE ): yield build(buffer) buffer, size = [], 0 buffer.append(unit) size += unit_size if buffer: yield build(buffer) @classmethod def batched(cls, messages: List[Accumulatable]): list_size = cls.LIST_SIZE or len(messages) budget = cls._max_message_size() buffer = [] size = 0 for message in cls._split_items(messages): message_size = cls._message_size(message) if buffer and ( size + message_size > budget or len(buffer) >= list_size ): yield buffer buffer, size = [], 0 buffer.append(message) size += message_size if buffer: yield buffer class MDSReportList(ShortenReprListMixin, Message, Reportable, Splittable): DEFAULT_METHOD = "MDS_SCAN_LIST" class MDSReport(Accumulatable): LIST_CLASS = MDSReportList class EnsureServiceState(Message): """Ensure the service has the appropriate status""" DEFAULT_METHOD = "ENSURE_SERVICE_STATE" class SensorWordpressIncidentList(MessageList, Reportable, Splittable): """Aggregated incident list""" DEFAULT_METHOD = "INCIDENT_LIST" class WordpressPluginAction(Message): DEFAULT_METHOD = "WP_SECURITY_PLUGIN_ACTION" class WordpressPluginTelemetry(Message, Reportable): """ Information about telemetry event related to Imunify Security WordPress plugin """ DEFAULT_METHOD = "WP_SECURITY_PLUGIN_EVENT" TARGET = ReportTarget.API def __repr__(self): return "{}()".format(self.__class__.__qualname__) class WPRuleDisabled(Message, Reportable): """WordPress protection rule disabled.""" DEFAULT_METHOD = "RULE_DISABLED" class WPRuleEnabled(Message, Reportable): """WordPress protection rule re-enabled.""" DEFAULT_METHOD = "RULE_ENABLED" class GeneralMetrics(MessageList, Reportable): DEFAULT_METHOD = "GENERAL_METRICS" defence360agent/contracts/myimunify_id.py0000644000000000000000000001341300000000000015624 0ustar import os import pwd import stat import uuid from pathlib import Path from typing import Dict, List, Optional from defence360agent.contracts.permissions import logger from defence360agent.model import instance from defence360agent.myimunify.model import MyImunify, update_users_protection from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.utils import safe_fileops MYIMUNIFY_ID_FILE_NAME = ".myimunify_id" _BANNER = ( "# DO NOT EDIT\n# This file contains MyImunify id unique to this user\n\n" ) _ID_LEN = 32 _HEX = frozenset("0123456789abcdef") class MyImunifyIdError(Exception): """Exception representing issues related to MyImunify id""" async def add_myimunify_user( sink, user: str, protection: bool ) -> Optional[str]: """Save subscription type to the DB and generate id file""" myimunify, _ = MyImunify.get_or_create(user=user) myimunify.save() await update_users_protection(sink, [user], protection) logger.info("Applied setting MyImunify=%s for user %s", protection, user) try: myimunify_id = await _get_or_generate_id(user) except MyImunifyIdError: # User no longer exists return None return myimunify_id async def get_myimunify_users() -> List[Dict]: """ Get a list of MyImunify users, their subscription types and unique ids """ users = [] user_details = await HostingPanel().get_user_details() myimunify_user_to_id = await _myimunify_user_to_id() with instance.db.transaction(): for user, myimunify_uid in sorted(myimunify_user_to_id.items()): record, _ = MyImunify.get_or_create(user=user) users.append( { "email": user_details.get(user, {}).get("email", ""), "username": user, "myimunify_id": myimunify_uid, "protection": record.protection, "locale": user_details.get(user, {}).get("locale", ""), } ) return users async def _myimunify_user_to_id() -> Dict[str, str]: """Get a list of users and their MyImunify ids""" user_to_id = {} for user in await HostingPanel().get_users(): try: user_to_id[user] = await _get_or_generate_id(user) except MyImunifyIdError: # User does not exist continue except safe_fileops.UnsafeFileOperation as e: logger.warning( "Unable to generate id for user=%s, error=%s", user, str(e) ) continue return user_to_id async def _get_or_generate_id(user: str) -> str: """ Read MyImunify id if exists and valid, or generate a new one and write into the file. Malformed files are regenerated. """ id_file = await _get_myimunify_id_file(user) try: return _read_id(id_file) except (FileNotFoundError, MyImunifyIdError): myimunify_id = uuid.uuid1().hex return await _write_id(myimunify_id, id_file) async def _write_id(myimunify_id: str, id_file: Path) -> str: """Write MyImunify id to file""" text = _BANNER + myimunify_id + "\n" try: await safe_fileops.write_text(str(id_file), text) except OSError as e: logger.warning("Unable to write myimunify_id in user home dir: %s", e) raise MyImunifyIdError from e return myimunify_id def _read_id(id_file: Path) -> str: """Read and validate MyImunify id from file. Raises MyImunifyIdError if malformed. Opens with O_RDONLY | O_NONBLOCK and verifies via fstat() that the fd refers to a regular file before reading. This eliminates the TOCTOU window between a path-level type check and the actual read (e.g. an attacker replacing the file with a FIFO between the two). """ try: fd = os.open(str(id_file), os.O_RDONLY | os.O_NONBLOCK) except FileNotFoundError: raise except OSError: raise MyImunifyIdError try: if not stat.S_ISREG(os.fstat(fd).st_mode): raise MyImunifyIdError data = os.read(fd, 8192) text = data.decode("utf-8") except UnicodeDecodeError: raise MyImunifyIdError finally: os.close(fd) return _parse_id(text) def _parse_id(text: str) -> str: """Read line by line: skip comments (#). First non-comment line must be valid id; nothing after it.""" id_line = None for line in text.splitlines(): s = line.strip() if not s: continue if s.startswith("#"): continue if id_line is not None: raise MyImunifyIdError if len(s) != _ID_LEN or not all(c in _HEX for c in s): raise MyImunifyIdError id_line = s if id_line is None: raise MyImunifyIdError return id_line async def _get_myimunify_id_file(user: str) -> Path: """Get a file with MyImunify id and create it if does not exist""" try: user_pwd = pwd.getpwnam(user) except KeyError as e: logger.warning("No such user: %s", user) raise MyImunifyIdError from e else: id_file = Path(user_pwd.pw_dir) / MYIMUNIFY_ID_FILE_NAME try: safe_fileops.ensure_regular_file(str(id_file)) except FileNotFoundError: if not id_file.parent.exists(): logger.warning("No such user homedir: %s", user) raise MyImunifyIdError try: await safe_fileops.touch(str(id_file)) except OSError as e: logger.warning( "Unable to put myimunify_id in user home dir: %s", e ) raise MyImunifyIdError from e except OSError: logger.warning("Cannot access identity file: %s", id_file) raise MyImunifyIdError return id_file defence360agent/contracts/permissions.py0000644000000000000000000001602100000000000015473 0ustar import logging from asyncio.coroutines import iscoroutinefunction from pathlib import Path from typing import Optional from defence360agent.contracts.config import ( MyImunifyConfig, PermissionsConfig, Wordpress, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.feature_management.constants import AV_REPORT, FULL from defence360agent.feature_management.model import FeatureManagementPerms from defence360agent.myimunify.model import MyImunify from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.subsys.panels.plesk import Plesk from defence360agent.utils import importer try: from imav.malwarelib.api.imunify_patch_subscription import ( ImunifyPatchSubscriptionAPI, ) except ImportError: ImunifyPatchSubscriptionAPI = None logger = logging.getLogger(__name__) PERMISSIONS = ( MS_VIEW, MS_CLEAN, MS_CLEAN_REQUIRES_MYIMUNIFY_PROTECTION, MS_ON_DEMAND_SCAN, MS_ON_DEMAND_SCAN_WITHOUT_RATE_LIMIT, MS_IGNORE_LIST_EDIT, MS_CONFIG_DEFAULT_ACTION_EDIT, MS_IMUNIFY_PATCH_ENABLED, MS_IMUNIFY_PATCH_ELIGIBLE_TO_PURCHASE, PD_VIEW, PD_CONFIG_MODE_EDIT, WP_WAF_EDIT, WP_WAF_RULES_EDIT, ) = ( "malware_scanner.view", "malware_scanner.clean", "malware_scanner.clean_requires_myimunify_protection", "malware_scanner.on_demand.scan", "malware_scanner.on_demand.scan_without_rate_limit", "malware_scanner.ignore_list.edit", "malware_scanner.config.default_action.edit", "malware_scanner.imunify_patch.enabled", "malware_scanner.imunify_patch.eligible_to_purchase", "proactive_defense.view", "proactive_defense.config.mode.edit", "wordpress.waf.edit", "wordpress.waf.rules.edit", ) GLOBAL_CONFDIR = Path("/etc/sysconfig/imunify360") def is_plesk_service_plan_enabled() -> bool: return ( HostingPanel().NAME == Plesk.NAME and PermissionsConfig.USE_PLESK_SERVICE_PLAN ) def myimunify_protection_enabled(user: Optional[str] = None) -> bool: return MyImunify.get_protection(user) def ms_view(user: Optional[str] = None) -> bool: if user is None: return True return FeatureManagementPerms.get_perm(user).av in ( AV_REPORT, FULL, ) def ms_clean(user: Optional[str] = None) -> bool: if LicenseCLN.is_free() or not LicenseCLN.is_valid(): return False if user is None: return True if is_plesk_service_plan_enabled(): # should be handled by Plesk extension return True return FeatureManagementPerms.get_perm(user).av == FULL def ms_clean_requires_myimunify_protection(user: Optional[str] = None): if MyImunifyConfig.ENABLED: return myimunify_protection_enabled(user) return ms_clean(user) def ms_on_demand_scan(user: Optional[str] = None) -> bool: if user is None: return True if MyImunifyConfig.ENABLED: # on-demand scan is available for both Basic and Pro subscriptions return True if is_plesk_service_plan_enabled(): # should be handled by Plesk extension return True return PermissionsConfig.ALLOW_MALWARE_SCAN def ms_on_demand_scan_without_rate_limit( user: Optional[str] = None, ) -> bool: if MyImunifyConfig.ENABLED: return myimunify_protection_enabled(user) return PermissionsConfig.ALLOW_MALWARE_SCAN def ms_ignore_list_edit(user: Optional[str] = None): if user is None: return True if MyImunifyConfig.ENABLED: # so far, MyImunify doesn't allow to the user editing ignore list return False return PermissionsConfig.USER_IGNORE_LIST def ms_config_default_action_edit(user: Optional[str] = None): if user is None: return True if MyImunifyConfig.ENABLED: # so far, MyImunify doesn't allow to the user # editing default malware action return False return PermissionsConfig.USER_OVERRIDE_MALWARE_ACTIONS ms_imunify_patch_enabled = importer.get( module="imav.contracts.permissions", name="is_imunify_patch_enabled", default=lambda _: False, ) has_imunify_patch_subscriptions = importer.get( module="imav.malwarelib.api.imunify_patch_subscription", name="has_imunify_patch_subscriptions", default=lambda _: False, ) async def ms_imunify_patch_eligible_to_purchase( user: str | None = None, ) -> bool: if ImunifyPatchSubscriptionAPI is None: return ( LicenseCLN.is_eligible_for_imunify_patch() or has_imunify_patch_subscriptions(user) ) return ( LicenseCLN.is_eligible_for_imunify_patch() or has_imunify_patch_subscriptions(user) or ( await ImunifyPatchSubscriptionAPI.get_purchase_eligibility() ).eligible ) def pd_view(user: Optional[str] = None): if user is None: return True return FeatureManagementPerms.get_perm(user).proactive == FULL def pd_config_mode_edit(user: Optional[str] = None): if user is None: return True if MyImunifyConfig.ENABLED: return False return PermissionsConfig.USER_OVERRIDE_PROACTIVE_DEFENSE def wp_waf_edit(user: Optional[str] = None): if user is None: return True if not Wordpress.SECURITY_PLUGIN_ENABLED: return False try: return bool(Wordpress.WAF_ENABLED) except KeyError: return True def wp_waf_rules_edit(user: Optional[str] = None): if user is None: return True try: return bool(PermissionsConfig.ALLOW_WP_WAF_RULES_MANAGEMENT) except KeyError: return True HAS_PERMISSION = { MS_VIEW: ms_view, MS_CLEAN: ms_clean, MS_CLEAN_REQUIRES_MYIMUNIFY_PROTECTION: ( ms_clean_requires_myimunify_protection ), MS_ON_DEMAND_SCAN: ms_on_demand_scan, MS_ON_DEMAND_SCAN_WITHOUT_RATE_LIMIT: ms_on_demand_scan_without_rate_limit, MS_IGNORE_LIST_EDIT: ms_ignore_list_edit, MS_CONFIG_DEFAULT_ACTION_EDIT: ms_config_default_action_edit, MS_IMUNIFY_PATCH_ENABLED: ms_imunify_patch_enabled, MS_IMUNIFY_PATCH_ELIGIBLE_TO_PURCHASE: ms_imunify_patch_eligible_to_purchase, PD_VIEW: pd_view, PD_CONFIG_MODE_EDIT: pd_config_mode_edit, WP_WAF_EDIT: wp_waf_edit, WP_WAF_RULES_EDIT: wp_waf_rules_edit, } async def has_permission(permission, user) -> bool: func = HAS_PERMISSION.get(permission) if func is None: return False if iscoroutinefunction(func): return await func(user) return func(user) async def check_permission(permission, user) -> None: func = HAS_PERMISSION.get(permission) if func is None: raise PermissionError("notifications.generalPermissionError") if iscoroutinefunction(func): if not await func(user): raise PermissionError("notifications.generalPermissionError") else: if not func(user): raise PermissionError("notifications.generalPermissionError") async def permissions_list(user) -> list[str]: return [ permission for permission in PERMISSIONS if await has_permission(permission, user) ] defence360agent/contracts/plugins.py0000644000000000000000000002020200000000000014575 0ustar import asyncio import inspect import logging import subprocess from abc import ABC, ABCMeta, abstractmethod from contextlib import suppress from functools import lru_cache, wraps from defence360agent.contracts.messages import Message, MessageType from defence360agent.utils import Scope logger = logging.getLogger(__name__) class BasePlugin(object): SCOPE = Scope.AV_IM360 SHUTDOWN_PRIORITY = 100 # lower means shuts down first AVAILABLE_ON_FREEMIUM = True _subclasses = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls._subclasses.append(cls) @classmethod def get_active_plugins(cls): # consider all non-abstract subclasses are active return [ plugin for plugin in cls._subclasses if not inspect.isabstract(plugin) ] async def shutdown(self): """Shutdown plugin's subsystems, cancel running tasks, clean iptables (if plugin is protector). It should be safe to assume that it is called after corresponding create_source if applicable. It is called only from the shutdown task that runs at most once, meaning shutdown() is never called twice. """ pass def __repr__(self): return "%s.%s" % (self.__class__.__module__, self.__class__.__name__) class MessageSource(BasePlugin, ABC): @abstractmethod async def create_source(self, loop, sink): """This method is a coroutine.""" class Sensor(MessageSource, ABC): """ Sensor is alias to MessageSource. """ async def create_source(self, loop, sink): """This method is a coroutine.""" return await self.create_sensor(loop, sink) @abstractmethod async def create_sensor(self, loop, sink): """This method is a coroutine.""" class LogStreamReader(Sensor, metaclass=ABCMeta): source_file = None # Limit of bytes consumed from stream # while trying to read one line (128 kB) _LIMIT = 2**17 _cmd = None async def create_sensor(self, loop, sink): self._loop = loop self._sink = sink self._cmd = None if not self.source_file: return self._cmd = ( "/usr/bin/tail", # follow beyond the end of the file "--follow=name", "-n0", # keep trying to open a file if it is inaccessible "--retry", self.source_file, ) self._child_process = await asyncio.create_subprocess_exec( *self._cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=0, limit=self._LIMIT, ) loop.create_task( self._infinite_read_and_proceed(self._child_process.stdout) ) async def shutdown(self): if self._cmd is not None: cmd, self._cmd = self._cmd, None logger.debug("Terminating child process [%s]", cmd) # child process dies from the same signal when agent # is run from console (not as --daemon) with suppress(ProcessLookupError): self._child_process.kill() rc = await self._child_process.wait() logger.debug( "Terminated child process [%s] with code [%d]", cmd, rc ) @abstractmethod async def _infinite_read_and_proceed(self, stream_reader): raise NotImplementedError class BaseMessageProcessor: @lru_cache(maxsize=1) def _message_processors(self): rv = [] for attr_str in dir(self): if attr_str.startswith("_"): continue # skip non-public attributes func = getattr(self, attr_str) if callable(func) and hasattr( func, "_decorated_for_process_message" ): rv.append(func) return rv async def process_message(self, message): logger.debug("Dispatching %r through %r...", message, self) for coro in self._message_processors(): result = await coro(message) if isinstance(result, Message): return result class MessageSink(BasePlugin, BaseMessageProcessor, ABC): class ProcessingOrder: # e.g. check is valid ipv4 PRE_PROCESS_MESSAGE = 10 # lfd plugin should process lfd alerts before other ignore plugins LFD = 18 # e.g. for ignore_alert_with_whitelisted_ip IGNORE_MESSAGE = 20 # Should be before check ip in graylist UNBLOCK_FROM_SUBNET = 30 # Check ip in the graylist already CHECK_IP_IN_GRAYLIST = 40 # Append ttl to alert GRAYLIST_TIMEOUT = 50 # Store graylist to db GRAYLIST_DB_FIXUP = 55 # this should run before IPSET_PROTECTOR IMPORT_EXPORT_WBLIST = 60 # make ml prediction before lazy_init ML_PREDICTION = 70 # the default DEFAULT = 80 IPSET_PROTECTOR = DEFAULT WEBSHIELD_PROTECTOR = 81 # should be run after ManageGrayList(DEFAULT) WHITELIST_UNBLOCKED = 90 # Synclist timestamp update SYNCLIST_UPDATE = 100 # post action POST_ACTION = 120 # event hook processing EVENT_HOOK = 150 # iContact ICONTACT_SENT = 200 # e.g. Accumulate POST_PROCESS_MESSAGE = 999 # alias for DEFAULT PROCESSING_ORDER = ProcessingOrder.DEFAULT @abstractmethod async def create_sink(self, loop): pass def expect(*message_type, async_lock=None, **expect_fields): """ @expect decorator for MessageSink.dosmth(message) async methods. MessageSink method will be called by MessageSink.process_message() if message_type and expect_fields match the message ones. @expect's can be stacked together and decision whether to call decorated coro is made by evaluating stacked @expect's with logical OR: @expect(MessageType.SensorAlert) # -- OR -- @expect(MessageType.SensorIncident, plugin_id='ossec') def protect(message): ... """ def decorate(coro): if getattr(coro, "__name__", "").startswith("_"): raise TypeError("{coro} is not public".format(coro=coro)) @wraps(coro) async def decorated(self, message): def match(): return isinstance(message, message_type) and all( message.get(k) == v for k, v in expect_fields.items() ) def is_stacked(coro): return hasattr(coro, "_decorated_for_process_message") def terminal(coro): if is_stacked(coro): return terminal(coro._decorated_for_process_message) return coro # process stacked decorators with logical OR if match(): if async_lock is True: await message.acquire() try: result = await terminal(coro)(self, message) except Exception as exc: if ( isinstance(message, MessageType.Lockable) and message.locked() ): message.release() raise exc else: if ( async_lock is False and isinstance(message, MessageType.Lockable) and message.locked() ): message.release() return result if is_stacked(coro): # Give next decorator a chance: logical OR return await coro(self, message) return None decorated._decorated_for_process_message = coro return decorated return decorate _plugin_registry = set() def thisguy(plugincls): """Register class as a plugin. >>> @thisguy >>> class ConcreteSink (MessageSink): >>> ... """ _plugin_registry.add(plugincls) return plugincls def theseguys(): """Enumerate classobj for registered plugins.""" return _plugin_registry defence360agent/contracts/sentry.py0000644000000000000000000000613400000000000014450 0ustar from pathlib import Path from subprocess import DEVNULL, CalledProcessError, check_output from typing import Any from defence360agent.utils import stub_unexpected_error def _run_cmd(cmd): try: out = check_output(cmd, stderr=DEVNULL) except (FileNotFoundError, CalledProcessError): return None return out.decode("utf-8", errors="ignore").strip() @stub_unexpected_error def _get_virtualization_type(): systemd_virt = _run_cmd(["systemd-detect-virt"]) if systemd_virt: return systemd_virt virt_what = _run_cmd(["virt-what"]) if virt_what: return virt_what demicode = _run_cmd(["dmidecode", "-s", "system-manufacturer"]) if demicode: return demicode return "fail to detect" @stub_unexpected_error def _get_total_ram(): import psutil return psutil.virtual_memory().total // 2**20 _TAGS = None def _tags(): global _TAGS if _TAGS is None: from defence360agent.utils import OsReleaseInfo _TAGS = { "av_version": None, "core_version": None, "version": None, "os_details": stub_unexpected_error(OsReleaseInfo.pretty_name)(), "ip": None, "hosting_panel": None, "total_ram": _get_total_ram(), "firewall": None, "strategy": None, "virtualization": _get_virtualization_type(), "server_id": None, "iaid": None, "name": None, "test_build_id": None, "test_build_job_id": None, "test_parent_build_id": None, } return _TAGS def set_firewall_type(firewall: str) -> None: _tags()["firewall"] = firewall def set_hosting_panel(panel: str) -> None: _tags()["hosting_panel"] = panel def set_strategy(strategy: str) -> None: _tags()["strategy"] = strategy def set_ip(ip: str) -> None: _tags()["ip"] = ip def set_product_name(product: str) -> None: _tags()["name"] = product def set_server_id(id: str | None) -> None: _tags()["server_id"] = id def set_iaid(iaid: str | None) -> None: _tags()["iaid"] = iaid def set_version(version: str) -> None: _tags()["version"] = version def set_av_version(version: str) -> None: _tags()["av_version"] = version def set_core_version(version: str) -> None: _tags()["core_version"] = version def tags() -> dict: return _tags().copy() def tag(name: str) -> Any: return _tags()[name] def set_test_env() -> None: """Set tags for sentry events about test environment.""" for file_name, tag in [ ( Path("/var/imunify360/TEST_BUILD_ID"), "test_build_id", ), ( Path("/var/imunify360/TEST_BUILD_JOB_ID"), "test_build_job_id", ), ( Path("/var/imunify360/TEST_PARENT_BUILD_ID"), "test_parent_build_id", ), ]: if file_name.exists(): try: _tags()[tag] = file_name.read_text().strip() except Exception: # Ignore errors on loading test env tags pass defence360agent/defence360.py0000644000000000000000000000766000000000000012753 0ustar import asyncio import logging import os import sys from pathlib import Path import defence360agent.internals.logger from defence360agent.contracts.config import Core as Config from defence360agent.rpc_tools.exceptions import ResponseError from defence360agent.simple_rpc import SUCCESS, SocketError from defence360agent.utils import is_root_user from defence360agent.utils.cli import ( EXIT_CODES, EXITCODE_GENERAL_ERROR, print_error, print_response, print_warnings, ) from defence360agent.utils.parsers import EnvParser, create_cli_parser from defence360agent.sentry import flush_sentry logger = logging.getLogger(__name__) RPM_TRANSACTION_LOCK = Path( "/var/lib/rpm-state/imunify360-transaction-in-progress" ) def main(rpc_handlers_init, cli_args): # get ready to start: set conservative umask os.umask(Config.FILE_UMASK) defence360agent.internals.logger.reconfigure() rpc_handlers_init() parser = create_cli_parser() args = parser.parse_args(args=cli_args) if args.log_config or os.environ.get("IMUNIFY360_LOGGING_CONFIG_FILE"): defence360agent.internals.logger.update_logging_config_from_file( args.log_config or os.environ.get("IMUNIFY360_LOGGING_CONFIG_FILE") ) if args.console_log_level: defence360agent.internals.logger.setConsoleLogLevel( args.console_log_level ) if hasattr(args, "completions_command"): from defence360agent.utils.completions import generate_completions print(generate_completions(parser, args.shell)) return if hasattr(args, "endpoint") and hasattr(args, "generate_endpoint_params"): try: cli_kwargs = args.generate_endpoint_params(args) envvar_kwargs = EnvParser.parse( os.environ, args.command, args.envvar_parameter_options, exclude=cli_kwargs, ) result, data = args.endpoint(**envvar_kwargs, **cli_kwargs) print_warnings(data) flush_sentry() if result == SUCCESS: print_response(args.command, data, args.json, args.verbose) else: print_error(result, data, args.json, args.verbose) sys.exit(EXIT_CODES[result]) except SocketError as e: print_response( None, {"items": "ERROR: {}".format(e)}, args.json, args.verbose ) sys.exit(EXITCODE_GENERAL_ERROR) else: print(parser.format_help()) def entrypoint(rpc_handlers_init): if not is_root_user(): logger.info("%s could be used by the root user only!", Config.NAME) print( "Imunify360 CLI is unavailable for non-root user", file=sys.stderr ) sys.exit(EXITCODE_GENERAL_ERROR) try: main(rpc_handlers_init, sys.argv[1:]) except KeyboardInterrupt: logger.warning("User pressed Ctrl+C, exiting...") sys.exit(EXITCODE_GENERAL_ERROR) except ResponseError as e: logger.error("Response error: %s", e) sys.exit(EXITCODE_GENERAL_ERROR) except ImportError as e: if RPM_TRANSACTION_LOCK.exists(): logger.error("RPM transaction is in progress. %s", e) print( "RPM transaction is in progress. Please, wait until it is " "finished and try again.", file=sys.stderr, ) sys.exit(EXITCODE_GENERAL_ERROR) else: logger.exception( "Unknown error happened. See logs for more information" ) sys.exit(EXITCODE_GENERAL_ERROR) except Exception: logger.exception( "Unknown error happened. See logs for more information" ) sys.exit(EXITCODE_GENERAL_ERROR) finally: # ensure loop is closed to prevent asyncio warning # (https://bugs.python.org/issue23548) asyncio.get_event_loop().close() defence360agent/feature_management/0000755000000000000000000000000000000000000014375 5ustar defence360agent/feature_management/__init__.py0000644000000000000000000000000000000000000016474 0ustar defence360agent/feature_management/__pycache__/0000755000000000000000000000000000000000000016605 5ustar defence360agent/feature_management/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000031500000000000024004 0ustar r_jdS)Nr`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/__init__.pyrsrdefence360agent/feature_management/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031500000000000023045 0ustar r_jdS)Nr`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/__init__.pyrsrdefence360agent/feature_management/__pycache__/checkers.cpython-311.opt-1.pyc0000644000000000000000000000655600000000000024051 0ustar r_j ddlmZddlmZddlmZddlmZddlm Z de dee d e fd Z dde d e d e fd Z dde d e de fdZde d e fdZdS))List)deepcopy)FeatureDisabledError)CONFIG_MAPPINGS)FeatureManagementPermsfeature permissionsuserc|dStj|}||}||vr$td||dS)a Raise exception if feature is disabled for user :param feature: feature name :param permissions: permissions required :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited from use of the feature Nz.Feature '{name}' is disabled for user '{user}')namer )rget_perm get_featurerformat)r r r permpermission_values `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/checkers.py check_featurer su | ! *4 0 0D''00{**" < C C4 D     +*Fsectionc|dS|tvrdS|t|vrdS t|t|||n#t$r|rYdSwxYwdS)at Check if section is allowed to be read or write by user :param feature: feature name :param user: user name :param section: section name to check :param raise_: True to raise exception, otherwise return True or False :return: True if config sections is allowed to user, False otherwise :raises FeatureDisabledError: raised if raise_=True NTF)rrr)r r rraise_s r check_configr"s |to%%tog...tgw7@$GGGG    uu 4s"A AANdatareturncjt|}tD]}|D]}t|||s||=|S)z Remove prohibited sections from user config :param data: config data :param user: user name :return: new config data )rrr)rr new_datar rs rconfig_cleanupr@sV~~H"&& & &Gw77 &W% & OrcJtD]}|D]}t|||ddS)z Raise exception if user is making changes on prohibited sections of config :param data: config data :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited T)rN)rr)rr r rs rconfig_validationr RsK#>> > >G $ = = = = = >>>r)F)N)typingrcopyr exceptionsr constantsrmodelrstrrrdictrr rrr)s,,,,,,&&&&&&)))))) 3 T#Y c    0#S3<SD$ >D > > > > > > >rdefence360agent/feature_management/__pycache__/checkers.cpython-311.pyc0000644000000000000000000000655600000000000023112 0ustar r_j ddlmZddlmZddlmZddlmZddlm Z de dee d e fd Z dde d e d e fd Z dde d e de fdZde d e fdZdS))List)deepcopy)FeatureDisabledError)CONFIG_MAPPINGS)FeatureManagementPermsfeature permissionsuserc|dStj|}||}||vr$td||dS)a Raise exception if feature is disabled for user :param feature: feature name :param permissions: permissions required :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited from use of the feature Nz.Feature '{name}' is disabled for user '{user}')namer )rget_perm get_featurerformat)r r r permpermission_values `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/checkers.py check_featurer su | ! *4 0 0D''00{**" < C C4 D     +*Fsectionc|dS|tvrdS|t|vrdS t|t|||n#t$r|rYdSwxYwdS)at Check if section is allowed to be read or write by user :param feature: feature name :param user: user name :param section: section name to check :param raise_: True to raise exception, otherwise return True or False :return: True if config sections is allowed to user, False otherwise :raises FeatureDisabledError: raised if raise_=True NTF)rrr)r r rraise_s r check_configr"s |to%%tog...tgw7@$GGGG    uu 4s"A AANdatareturncjt|}tD]}|D]}t|||s||=|S)z Remove prohibited sections from user config :param data: config data :param user: user name :return: new config data )rrr)rr new_datar rs rconfig_cleanupr@sV~~H"&& & &Gw77 &W% & OrcJtD]}|D]}t|||ddS)z Raise exception if user is making changes on prohibited sections of config :param data: config data :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited T)rN)rr)rr r rs rconfig_validationr RsK#>> > >G $ = = = = = >>>r)F)N)typingrcopyr exceptionsr constantsrmodelrstrrrdictrr rrr)s,,,,,,&&&&&&)))))) 3 T#Y c    0#S3<SD$ >D > > > > > > >rdefence360agent/feature_management/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000134000000000000024260 0ustar r_j/pdZdZdZdZdZdZedeegiiZdZeded iZeeeeeeiZ eed zfZ d S) proactiveavnafullreportlogPROACTIVE_DEFENCE imunify360 imunify360_avimunify360_proactivez.tt2N) PROACTIVEAVNAFULL AV_REPORTLOGCONFIG_MAPPINGSNATIVE_EXTENSION_NAMEFEATURE_EXT_VARIABLESEXTENSION_DEFAULTS1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILESa/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/constants.pyrs        dC[% % "y)$d F"5111rdefence360agent/feature_management/__pycache__/constants.cpython-311.pyc0000644000000000000000000000134000000000000023321 0ustar r_j/pdZdZdZdZdZdZedeegiiZdZeded iZeeeeeeiZ eed zfZ d S) proactiveavnafullreportlogPROACTIVE_DEFENCE imunify360 imunify360_avimunify360_proactivez.tt2N) PROACTIVEAVNAFULL AV_REPORTLOGCONFIG_MAPPINGSNATIVE_EXTENSION_NAMEFEATURE_EXT_VARIABLESEXTENSION_DEFAULTS1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILESa/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/constants.pyrs        dC[% % "y)$d F"5111rdefence360agent/feature_management/__pycache__/control.cpython-311.opt-1.pyc0000644000000000000000000001072300000000000023731 0ustar r_j ddlZddlZddlmZddlmZmZmZmZddl m Z ddl m Z ddl mZejeZdZdZed Zed Zed ZdS) N)Version)EXTENSION_DEFAULTSFEATURE_EXT_VARIABLESNATIVE_EXTENSION_NAME1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES)reset_features)cPanel) HostingPanelcFtjfd}|S)z-Do not run a function on an unsupported panelcKtd{Vr|i|d{VStddS)Nz*Native feature management is not supported)&is_native_feature_management_supportedloggerinfo)argskwargsfuncs _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/control.pywrapperzsupported..wrappersg799 9 9 9 9 9 9 /t.v........ . @AAAAA) functoolswraps)rrs` r supportedrs?_TBBBBB NrcKt}|jtjkrJ t|d{VtdkS#t $rYdSwxYwdS)z:Whether we support native feature management on the panel.Nz68.0F)r NAMEr rversion ValueErrorhps rr r s B w&+ ------..'&//A A   55  5s7A A-,A-cKt}|to|d{VS)z1Whether the native feature management is enabled.)pkgsN)r is_extension_installedris_hook_installedrs r$is_native_feature_management_enabledr#+sY B !!B "   )&&(( ( ( ( ( ( ( rcKt}tdidtjDd{V|jt t fitd{Vt ddS)z!Enable native feature management.c0i|]\}}|t|S)r).0featurepe_vars r z4enable_native_feature_management..@s4    '/   rNz-Imunify360 native feature management enabled.r&) r rritemsinstall_extensionrrrrrrs r enable_native_feature_managementr-8s B     #8#>#@#@      " 9       KK?@@@@@rcKtd{VstddStt t d{VtddS)z"Disable native feature management.Nz,No Imunify360 package extensions to disable.Tz.Imunify360 native feature management disabled.)r#rrr uninstall_extensionrrr&rr!disable_native_feature_managementr0Os677 7 7 7 7 7 7 BCCCt .. , ,9    KK@AAA 4r)rloggingpackaging.versionr,defence360agent.feature_management.constantsrrrr(defence360agent.feature_management.utilsr$defence360agent.subsys.panels.cpanelr +defence360agent.subsys.panels.hosting_panelr getLogger__name__rrr r#r-r0r&rrr9s/%%%%%% DCCCCC777777DDDDDD  8 $ $            AA A,       rdefence360agent/feature_management/__pycache__/control.cpython-311.pyc0000644000000000000000000001072300000000000022772 0ustar r_j ddlZddlZddlmZddlmZmZmZmZddl m Z ddl m Z ddl mZejeZdZdZed Zed Zed ZdS) N)Version)EXTENSION_DEFAULTSFEATURE_EXT_VARIABLESNATIVE_EXTENSION_NAME1NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES)reset_features)cPanel) HostingPanelcFtjfd}|S)z-Do not run a function on an unsupported panelcKtd{Vr|i|d{VStddS)Nz*Native feature management is not supported)&is_native_feature_management_supportedloggerinfo)argskwargsfuncs _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/control.pywrapperzsupported..wrappersg799 9 9 9 9 9 9 /t.v........ . @AAAAA) functoolswraps)rrs` r supportedrs?_TBBBBB NrcKt}|jtjkrJ t|d{VtdkS#t $rYdSwxYwdS)z:Whether we support native feature management on the panel.Nz68.0F)r NAMEr rversion ValueErrorhps rr r s B w&+ ------..'&//A A   55  5s7A A-,A-cKt}|to|d{VS)z1Whether the native feature management is enabled.)pkgsN)r is_extension_installedris_hook_installedrs r$is_native_feature_management_enabledr#+sY B !!B "   )&&(( ( ( ( ( ( ( rcKt}tdidtjDd{V|jt t fitd{Vt ddS)z!Enable native feature management.c0i|]\}}|t|S)r).0featurepe_vars r z4enable_native_feature_management..@s4    '/   rNz-Imunify360 native feature management enabled.r&) r rritemsinstall_extensionrrrrrrs r enable_native_feature_managementr-8s B     #8#>#@#@      " 9       KK?@@@@@rcKtd{VstddStt t d{VtddS)z"Disable native feature management.Nz,No Imunify360 package extensions to disable.Tz.Imunify360 native feature management disabled.)r#rrr uninstall_extensionrrr&rr!disable_native_feature_managementr0Os677 7 7 7 7 7 7 BCCCt .. , ,9    KK@AAA 4r)rloggingpackaging.versionr,defence360agent.feature_management.constantsrrrr(defence360agent.feature_management.utilsr$defence360agent.subsys.panels.cpanelr +defence360agent.subsys.panels.hosting_panelr getLogger__name__rrr r#r-r0r&rrr9s/%%%%%% DCCCCC777777DDDDDD  8 $ $            AA A,       rdefence360agent/feature_management/__pycache__/exceptions.cpython-311.opt-1.pyc0000644000000000000000000000202600000000000024427 0ustar r_j2ZGddeZGddeZGddeZdS)ceZdZdZdS)FeatureManagementErrorz%Base exception for feature managementN__name__ __module__ __qualname____doc__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/exceptions.pyrrs////r rceZdZdZdS)FeatureDisabledErrorzFeature is disabled for userNrr r r r r s&&&&r r ceZdZdZdS)UserArgumentNotFoundz;Method/function lack the parameter which contains user nameNrr r r rr sEEEEr rN) Exceptionrr rr r r rs00000Y000'''''1'''FFFFF1FFFFFr defence360agent/feature_management/__pycache__/exceptions.cpython-311.pyc0000644000000000000000000000202600000000000023470 0ustar r_j2ZGddeZGddeZGddeZdS)ceZdZdZdS)FeatureManagementErrorz%Base exception for feature managementN__name__ __module__ __qualname____doc__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/exceptions.pyrrs////r rceZdZdZdS)FeatureDisabledErrorzFeature is disabled for userNrr r r r r s&&&&r r ceZdZdZdS)UserArgumentNotFoundz;Method/function lack the parameter which contains user nameNrr r r rr sEEEEr rN) Exceptionrr rr r r rs00000Y000'''''1'''FFFFF1FFFFFr defence360agent/feature_management/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000000763400000000000023403 0ustar r_j &dZddlZddlZddlmZmZmZddlmZddl m Z m Z m Z m Z ejeZdZdZedeed ed efd Ze de d iZedeed ed efd Ze ee eiZded eeeegeffdZdS)a This module contains hook, which are called on feature management permission changes. Note that hooks are not executed automatically, developer is responsible to obtain specific hook using get_hook() function and call it. To add hook, create function with name equal to feature name N)AnyCallableOptional) ConfigFile)AVFULLLOG PROACTIVEcdS)NT)_s ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/hooks.py _hook_stubrs 4cFtjfd}|S)Nch||}|p!tdj|||S)Nz#Hook '%s(%s)' failed for user '%s'.)loggerwarning__name__)uservalueresultcallbacks rwrapz_result_warn..wrapsC$&& &.. 1        r) functoolswraps)rrs` r _result_warnrs8_X Krrrreturncp|sdSt}|dd}t|}|dd}|tkrd}n0|r.|dr|r|drd} |dd|n#t $rYdSwxYwdS)z#Called when 'av' feature is changedTMALWARE_SCANNINGdefault_actionNcleanupnotifyF)rgetr startswithset Exception)rrconfig config_value user_configuser_config_values r antivirusr,)s t \\F::02BCCLT""K#(:r?s **********777777  8 $ $   HSM#$@ $! HSM # $       y  *c*h s';T'AB******rdefence360agent/feature_management/__pycache__/hooks.cpython-311.pyc0000644000000000000000000000763400000000000022444 0ustar r_j &dZddlZddlZddlmZmZmZddlmZddl m Z m Z m Z m Z ejeZdZdZedeed ed efd Ze de d iZedeed ed efd Ze ee eiZded eeeegeffdZdS)a This module contains hook, which are called on feature management permission changes. Note that hooks are not executed automatically, developer is responsible to obtain specific hook using get_hook() function and call it. To add hook, create function with name equal to feature name N)AnyCallableOptional) ConfigFile)AVFULLLOG PROACTIVEcdS)NT)_s ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/hooks.py _hook_stubrs 4cFtjfd}|S)Nch||}|p!tdj|||S)Nz#Hook '%s(%s)' failed for user '%s'.)loggerwarning__name__)uservalueresultcallbacks rwrapz_result_warn..wrapsC$&& &.. 1        r) functoolswraps)rrs` r _result_warnrs8_X Krrrreturncp|sdSt}|dd}t|}|dd}|tkrd}n0|r.|dr|r|drd} |dd|n#t $rYdSwxYwdS)z#Called when 'av' feature is changedTMALWARE_SCANNINGdefault_actionNcleanupnotifyF)rgetr startswithset Exception)rrconfig config_value user_configuser_config_values r antivirusr,)s t \\F::02BCCLT""K#(:r?s **********777777  8 $ $   HSM#$@ $! HSM # $       y  *c*h s';T'AB******rdefence360agent/feature_management/__pycache__/lookup.cpython-311.opt-1.pyc0000644000000000000000000001066300000000000023565 0ustar r_j ddlZddlmZmZmZddlmZddlmZddl m Z ddl m Z dd l mZeZd ed eed ed efdeded eff dZ dd ed eedeegeffdZdS)N)AnyCallableList)MyImunifyConfig)is_plesk_service_plan_enabled)wraps) check_feature)UserArgumentNotFoundname permissionsfunc.user_keyreturncR tj}|jvrtd|j j ju fdt fd}t fd}tjr|S|S)z Wrapper to enable feature management for func :param name: feature name :param func: function/method to wrap :param user_key: parameter name which contains user name :return: new callable object zExpecting argument '%s' for %scr|vrtdtjr dStr dS|j}t |dS)Nz3Argument '%s' for '%s' must be specified explicitly)r rENABLEDrgetdefaultr )kwargsuserrr rruser_key_required user_params ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/lookup.pycheckerz_wrapper..checker!s  !7!7&E   "  > F ( * *  G Fzz(J$677dK.....c$di||i|SNr argsrrrs rwrapperz_wrapper..wrapper4s.&tT$V$$$rc4Kdi||i|d{VSrr r!s r async_wrapperz_wrapper..async_wrapper9sD&T4*6*********r)inspect signature parametersr remptyr iscoroutinefunction) r rrrr'r#r%rrrs ```` @@@r_wrapperr+ s!$''Iy+++" ,h   %h/J"*j.>>//////////& 4[[%%%%%[% 4[[+++++[+"4(( Nrrcfd}|S)a  Get decorator to manage function/method with feature management :param name: feature name :param user_key: parameter name which contains user name :param permissions: list of permission values, with which user can access specifig endpoint :return: decorator ctj|rvt|diD]Q\}}|ds7tj|r#t |}t|||Rn&tj|rt |}t |S)N__dict___) r&isclassgetattritems startswith isfunctionr+setattrfeaturesadd)objm_namem_objr#r rrs r decoratorzfeature..decoratorPs ?3   =!(j"!=!=!C!C!E!E 2 2 ((--2'2DU2K2K2&t[%JJGC111 2  $ $ =4c8<rEs3&&&&&&&&&&......AAAAAA$$$$$$######,,,,,, 3553 3 I3-5c3h-?3KN3 c3h3333n17  I seSjrdefence360agent/feature_management/__pycache__/lookup.cpython-311.pyc0000644000000000000000000001066300000000000022626 0ustar r_j ddlZddlmZmZmZddlmZddlmZddl m Z ddl m Z dd l mZeZd ed eed ed efdeded eff dZ dd ed eedeegeffdZdS)N)AnyCallableList)MyImunifyConfig)is_plesk_service_plan_enabled)wraps) check_feature)UserArgumentNotFoundname permissionsfunc.user_keyreturncR tj}|jvrtd|j j ju fdt fd}t fd}tjr|S|S)z Wrapper to enable feature management for func :param name: feature name :param func: function/method to wrap :param user_key: parameter name which contains user name :return: new callable object zExpecting argument '%s' for %scr|vrtdtjr dStr dS|j}t |dS)Nz3Argument '%s' for '%s' must be specified explicitly)r rENABLEDrgetdefaultr )kwargsuserrr rruser_key_required user_params ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/lookup.pycheckerz_wrapper..checker!s  !7!7&E   "  > F ( * *  G Fzz(J$677dK.....c$di||i|SNr argsrrrs rwrapperz_wrapper..wrapper4s.&tT$V$$$rc4Kdi||i|d{VSrr r!s r async_wrapperz_wrapper..async_wrapper9sD&T4*6*********r)inspect signature parametersr remptyr iscoroutinefunction) r rrrr'r#r%rrrs ```` @@@r_wrapperr+ s!$''Iy+++" ,h   %h/J"*j.>>//////////& 4[[%%%%%[% 4[[+++++[+"4(( Nrrcfd}|S)a  Get decorator to manage function/method with feature management :param name: feature name :param user_key: parameter name which contains user name :param permissions: list of permission values, with which user can access specifig endpoint :return: decorator ctj|rvt|diD]Q\}}|ds7tj|r#t |}t|||Rn&tj|rt |}t |S)N__dict___) r&isclassgetattritems startswith isfunctionr+setattrfeaturesadd)objm_namem_objr#r rrs r decoratorzfeature..decoratorPs ?3   =!(j"!=!=!C!C!E!E 2 2 ((--2'2DU2K2K2&t[%JJGC111 2  $ $ =4c8<rEs3&&&&&&&&&&......AAAAAA$$$$$$######,,,,,, 3553 3 I3-5c3h-?3KN3 c3h3333n17  I seSjrdefence360agent/feature_management/__pycache__/model.cpython-311.opt-1.pyc0000644000000000000000000000755100000000000023356 0ustar r_j| fddlmZmZmZddlmZmZmZmZm Z m Z ddl m Z m Z Gdde ZdS)) CharFieldCheck TextField)AV AV_REPORTFULLLOGNA PROACTIVE)Modelinstancec ^eZdZdZdZGddZedZede d e e e ge Zede d e ee ge Zed ed dfd ZedZdZded efdZdedefdZdZdS)FeatureManagementPermszrPermissions state for Feature Management. Each record/instance is a set of permissions of a single user. c eZdZejZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__r dbdatabasedb_table]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/model.pyMetars;3rrT)uniqueFzproactive in ('{}','{}','{}'))null constraintsdefaultzav in ('{}','{}','{}')userreturnc|}||St|jt|ji}|||\}}|S)z Get feature permissions by user name :param user: user name :return: :class:`FeatureManagementPerms` object for user N)r"defaults) get_defaultravr proactive get_or_create)clsr"r!r%perm_s rget_permzFeatureManagementPerms.get_perm0sX//## <N  w(  ###AAa rc8||jS)zGet default permissions)r")getDEFAULT)r*s rr&z"FeatureManagementPerms.get_defaultEswwCKw(((rc"|j|jkS)z&Check if current permission is default)r"r0selfs r is_defaultz!FeatureManagementPerms.is_defaultJsyDL((rkeyc"t||S)zGet permission by feature name)getattr)r3r5s r get_featurez"FeatureManagementPerms.get_featureNstS!!!rvaluecPt||||dS)zSet permissionN)setattrsave)r3r5r9s r set_featurez"FeatureManagementPerms.set_featureRs%c5!!! rc6t|jt|jiS)N)rr'r r(r2s ras_dictzFeatureManagementPerms.as_dictWs  t~  rN)rrr__doc__r0rrr"rrformatr r rr(rr' classmethodstrr-r&r4r8r=r?rrrrrs G44444444 9D ! ! !D  E188S$GG H H  I   E*11"iFF G G     BC$<[())[))))"s"s""""s3      rrN)peeweerrr,defence360agent.feature_management.constantsrrrr r r defence360agent.modelr r rrrrrGs..........21111111M M M M M UM M M M M rdefence360agent/feature_management/__pycache__/model.cpython-311.pyc0000644000000000000000000000755100000000000022417 0ustar r_j| fddlmZmZmZddlmZmZmZmZm Z m Z ddl m Z m Z Gdde ZdS)) CharFieldCheck TextField)AV AV_REPORTFULLLOGNA PROACTIVE)Modelinstancec ^eZdZdZdZGddZedZede d e e e ge Zede d e ee ge Zed ed dfd ZedZdZded efdZdedefdZdZdS)FeatureManagementPermszrPermissions state for Feature Management. Each record/instance is a set of permissions of a single user. c eZdZejZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__r dbdatabasedb_table]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/model.pyMetars;3rrT)uniqueFzproactive in ('{}','{}','{}'))null constraintsdefaultzav in ('{}','{}','{}')userreturnc|}||St|jt|ji}|||\}}|S)z Get feature permissions by user name :param user: user name :return: :class:`FeatureManagementPerms` object for user N)r"defaults) get_defaultravr proactive get_or_create)clsr"r!r%perm_s rget_permzFeatureManagementPerms.get_perm0sX//## <N  w(  ###AAa rc8||jS)zGet default permissions)r")getDEFAULT)r*s rr&z"FeatureManagementPerms.get_defaultEswwCKw(((rc"|j|jkS)z&Check if current permission is default)r"r0selfs r is_defaultz!FeatureManagementPerms.is_defaultJsyDL((rkeyc"t||S)zGet permission by feature name)getattr)r3r5s r get_featurez"FeatureManagementPerms.get_featureNstS!!!rvaluecPt||||dS)zSet permissionN)setattrsave)r3r5r9s r set_featurez"FeatureManagementPerms.set_featureRs%c5!!! rc6t|jt|jiS)N)rr'r r(r2s ras_dictzFeatureManagementPerms.as_dictWs  t~  rN)rrr__doc__r0rrr"rrformatr r rr(rr' classmethodstrr-r&r4r8r=r?rrrrrs G44444444 9D ! ! !D  E188S$GG H H  I   E*11"iFF G G     BC$<[())[))))"s"s""""s3      rrN)peeweerrr,defence360agent.feature_management.constantsrrrr r r defence360agent.modelr r rrrrrGs..........21111111M M M M M UM M M M M rdefence360agent/feature_management/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000001664600000000000023423 0ustar r_j ddlZddlZddlmZddlmZmZddlmZddl m Z ddl m Z ddl mZddlmZmZmZd d lmZejeZd ed ed edefdZd edeed edeefdZd ed efdZdeedefdZdZdS)N)chain)ListAny)Core)hooks)FeatureManagementPerms)instance)execute_iterable_expressionis_safe_subdir_namermtree)featuresuserfeaturevaluereturncKtj5}tj|}|||t j|}|||}|rt d|||n1t d|||| |cdddS#1swxYwYdS)zSets a `feature` to `value` for a given `user`. Calls appropriate hook and returns its (bool) result. Logs the result of setting change. If hook fails rollbacks changes to database. !Applied setting %s=%s for user %s)Failed to apply setting %s=%s for user %sN) r dbatomicrget_perm set_featurerget_hookloggerinfoerrorrollback)rrrtrxpermhookoks ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/utils.pyrrs%     %.t44 %(((~g&& T$    KK3WeT     LL;     LLNNN#sBC  CCusersexisting_userscKggd}|D]p}||vrtd|"t|||d{Vr|d|U|d|q|S)N) succeededfailedzNo such user: %sr'r()rwarningrappend)rr$rr%resultrs r# update_usersr,.s , ,F** ~ % % NN-t 4 4 4 T7E22 2 2 2 2 2 2 * ;  & &t , , , , 8  # #D ) ) ) ) McKtj}|||tj|}|d|SN)r get_defaultrrr)rrr r!s r#update_defaultr1@sK ! - / /DWe$$$ >' " "D 4e  r-cKt|}tjtj}tt |}||z }|tj|rt d|d}t|t||D]}t|stjt j|} t%|M#t&$rYYt($r&}td||Yd}~d}~wwxYw||z }|rt d||D]U}tj|} t.D]7} | | } t3j| } | || 8Vt7|pt7|S)z1Synchronize existing permissions with panel userszRemove permissions of users %sctjtj|Sr/)rdeletewhererin_)perms_to_removes r# expressionzsync_users..expressionTs7)02288&+//@@ r-z'Failed to remove user_config dir %s: %sNzAdd permissions to users %s)setrselectrrtuplesremoveDEFAULTrrr listr ospathjoinr USER_CONFDIRr FileNotFoundErrorOSErrorr)rr get_featurerrbool) r$ panel_users perm_usersr7r8rtargete perms_to_addr rrcallbacks r# sync_usersrMGse**K'./E/JKKJUJ--//011J ;.O19::: 4oFFF    $J_0E0EFFF#  D&t,, W\\$"3T::F v$      =vq  +LA 1<@@@""%.t44 " "G$$W--E~g..H HT5 ! ! ! ! "    6o!6!66s8D E ED>>Ec4Ktttjtjtjtjk}d}|jD]\}}tj |}g}|D]A}|||r| |$t d|||Bt|||||D]}td||| dS)zlSets feature values for all existing users in feature management database to given values in `features`.ctjdi||itj|S)N)rupdater5rr6)chunkrrs r#r8z"reset_features..expressionsD%,@@/?@@FF " ' + +E 2 2   r-rrN)r>rrr:rr5r=r;itemsrrr*rrr r)rr$r8rrr!appliedrs r#reset_featuresrUwsU  # *+A+F G G U&+/E/MMVXX    E   )(.**~g&&   DtD%   t$$$$ ?  $J%HHH  D KK3WeT     )r-)loggingr? itertoolsrtypingrr defence360agent.contracts.configr"defence360agent.feature_managementr(defence360agent.feature_management.modelrdefence360agent.modelr defence360agent.utilsr r r lookupr getLogger__name__rstrrFrr,r1rMrUrPr-r#rbs 111111444444KKKKKK******   8 $ $C#cd4 c+.@DS $#c-7DI-7$-7-7-7-7`)))))r-defence360agent/feature_management/__pycache__/utils.cpython-311.pyc0000644000000000000000000001664600000000000022464 0ustar r_j ddlZddlZddlmZddlmZmZddlmZddl m Z ddl m Z ddl mZddlmZmZmZd d lmZejeZd ed ed edefdZd edeed edeefdZd ed efdZdeedefdZdZdS)N)chain)ListAny)Core)hooks)FeatureManagementPerms)instance)execute_iterable_expressionis_safe_subdir_namermtree)featuresuserfeaturevaluereturncKtj5}tj|}|||t j|}|||}|rt d|||n1t d|||| |cdddS#1swxYwYdS)zSets a `feature` to `value` for a given `user`. Calls appropriate hook and returns its (bool) result. Logs the result of setting change. If hook fails rollbacks changes to database. !Applied setting %s=%s for user %s)Failed to apply setting %s=%s for user %sN) r dbatomicrget_perm set_featurerget_hookloggerinfoerrorrollback)rrrtrxpermhookoks ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/utils.pyrrs%     %.t44 %(((~g&& T$    KK3WeT     LL;     LLNNN#sBC  CCusersexisting_userscKggd}|D]p}||vrtd|"t|||d{Vr|d|U|d|q|S)N) succeededfailedzNo such user: %sr'r()rwarningrappend)rr$rr%resultrs r# update_usersr,.s , ,F** ~ % % NN-t 4 4 4 T7E22 2 2 2 2 2 2 * ;  & &t , , , , 8  # #D ) ) ) ) McKtj}|||tj|}|d|SN)r get_defaultrrr)rrr r!s r#update_defaultr1@sK ! - / /DWe$$$ >' " "D 4e  r-cKt|}tjtj}tt |}||z }|tj|rt d|d}t|t||D]}t|stjt j|} t%|M#t&$rYYt($r&}td||Yd}~d}~wwxYw||z }|rt d||D]U}tj|} t.D]7} | | } t3j| } | || 8Vt7|pt7|S)z1Synchronize existing permissions with panel userszRemove permissions of users %sctjtj|Sr/)rdeletewhererin_)perms_to_removes r# expressionzsync_users..expressionTs7)02288&+//@@ r-z'Failed to remove user_config dir %s: %sNzAdd permissions to users %s)setrselectrrtuplesremoveDEFAULTrrr listr ospathjoinr USER_CONFDIRr FileNotFoundErrorOSErrorr)rr get_featurerrbool) r$ panel_users perm_usersr7r8rtargete perms_to_addr rrcallbacks r# sync_usersrMGse**K'./E/JKKJUJ--//011J ;.O19::: 4oFFF    $J_0E0EFFF#  D&t,, W\\$"3T::F v$      =vq  +LA 1<@@@""%.t44 " "G$$W--E~g..H HT5 ! ! ! ! "    6o!6!66s8D E ED>>Ec4Ktttjtjtjtjk}d}|jD]\}}tj |}g}|D]A}|||r| |$t d|||Bt|||||D]}td||| dS)zlSets feature values for all existing users in feature management database to given values in `features`.ctjdi||itj|S)N)rupdater5rr6)chunkrrs r#r8z"reset_features..expressionsD%,@@/?@@FF " ' + +E 2 2   r-rrN)r>rrr:rr5r=r;itemsrrr*rrr r)rr$r8rrr!appliedrs r#reset_featuresrUwsU  # *+A+F G G U&+/E/MMVXX    E   )(.**~g&&   DtD%   t$$$$ ?  $J%HHH  D KK3WeT     )r-)loggingr? itertoolsrtypingrr defence360agent.contracts.configr"defence360agent.feature_managementr(defence360agent.feature_management.modelrdefence360agent.modelr defence360agent.utilsr r r lookupr getLogger__name__rstrrFrr,r1rMrUrPr-r#rbs 111111444444KKKKKK******   8 $ $C#cd4 c+.@DS $#c-7DI-7$-7-7-7-7`)))))r-defence360agent/feature_management/checkers.py0000644000000000000000000000471400000000000016544 0ustar from typing import List from copy import deepcopy from .exceptions import FeatureDisabledError from .constants import CONFIG_MAPPINGS from .model import FeatureManagementPerms def check_feature(feature: str, permissions: List[str], user: str): """ Raise exception if feature is disabled for user :param feature: feature name :param permissions: permissions required :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited from use of the feature """ if user is None: return perm = FeatureManagementPerms.get_perm(user) permission_value = perm.get_feature(feature) if permission_value not in permissions: raise FeatureDisabledError( "Feature '{name}' is disabled for user '{user}'".format( name=feature, user=user ) ) def check_config(feature: str, user: str, section: str, raise_=False): """ Check if section is allowed to be read or write by user :param feature: feature name :param user: user name :param section: section name to check :param raise_: True to raise exception, otherwise return True or False :return: True if config sections is allowed to user, False otherwise :raises FeatureDisabledError: raised if raise_=True """ if user is None: return True if feature not in CONFIG_MAPPINGS: return True if section not in CONFIG_MAPPINGS[feature]: return True try: check_feature(feature, CONFIG_MAPPINGS[feature][section], user) except FeatureDisabledError: if raise_: raise return False return True def config_cleanup(data: dict, user: str = None) -> dict: """ Remove prohibited sections from user config :param data: config data :param user: user name :return: new config data """ new_data = deepcopy(data) for feature in CONFIG_MAPPINGS: for section in data: if not check_config(feature, user, section): del new_data[section] return new_data def config_validation(data: dict, user: str): """ Raise exception if user is making changes on prohibited sections of config :param data: config data :param user: user name :return: None :raises FeatureDisabledError: if user is prohibited """ for feature in CONFIG_MAPPINGS: for section in data: check_config(feature, user, section, raise_=True) defence360agent/feature_management/constants.py0000644000000000000000000000205700000000000016767 0ustar # feature name constants PROACTIVE = "proactive" AV = "av" #: Not available permissions NA = "na" #: Full permissions FULL = "full" #: Report only permission for AV feature AV_REPORT = "report" #: Log-only permission for PROACTIVE feature. #: User retains the feature in observation mode (PROACTIVE_DEFENCE.mode=LOG) #: instead of having it fully disabled. Selected by the #: FEATURE_MANAGEMENT.proactive_disable_target config toggle. LOG = "log" # config sections related to feature CONFIG_MAPPINGS = { PROACTIVE: { "PROACTIVE_DEFENCE": [FULL, LOG], }, } # Native FM panel extension name NATIVE_EXTENSION_NAME = "imunify360" # Mapping of feature names to extension variables FEATURE_EXT_VARIABLES = { AV: "imunify360_av", PROACTIVE: "imunify360_proactive", } # Mapping of extension variable to default value EXTENSION_DEFAULTS = { FEATURE_EXT_VARIABLES[AV]: AV_REPORT, FEATURE_EXT_VARIABLES[PROACTIVE]: FULL, } NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES = ( NATIVE_EXTENSION_NAME, NATIVE_EXTENSION_NAME + ".tt2", ) defence360agent/feature_management/control.py0000644000000000000000000000477400000000000016443 0ustar import functools import logging from packaging.version import Version from defence360agent.feature_management.constants import ( EXTENSION_DEFAULTS, FEATURE_EXT_VARIABLES, NATIVE_EXTENSION_NAME, NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES, ) from defence360agent.feature_management.utils import reset_features from defence360agent.subsys.panels.cpanel import cPanel from defence360agent.subsys.panels.hosting_panel import HostingPanel logger = logging.getLogger(__name__) def supported(func): """Do not run a function on an unsupported panel""" @functools.wraps(func) async def wrapper(*args, **kwargs): if await is_native_feature_management_supported(): return await func(*args, **kwargs) logger.info("Native feature management is not supported") return wrapper async def is_native_feature_management_supported(): """Whether we support native feature management on the panel.""" hp = HostingPanel() if hp.NAME == cPanel.NAME: try: return Version(await hp.version()) >= Version("68.0") except ValueError: return False return False @supported async def is_native_feature_management_enabled(): """Whether the native feature management is enabled.""" hp = HostingPanel() return ( hp.is_extension_installed( pkgs=NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES ) and await hp.is_hook_installed() ) @supported async def enable_native_feature_management(): """Enable native feature management.""" hp = HostingPanel() # reset feature values for all existing users await reset_features( **{ feature: EXTENSION_DEFAULTS[pe_var] for feature, pe_var in FEATURE_EXT_VARIABLES.items() } ) await hp.install_extension( NATIVE_EXTENSION_NAME, NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES, **EXTENSION_DEFAULTS, ) logger.info("Imunify360 native feature management enabled.") @supported async def disable_native_feature_management(): """Disable native feature management.""" if not await is_native_feature_management_enabled(): logger.info("No Imunify360 package extensions to disable.") return True await HostingPanel().uninstall_extension( NATIVE_EXTENSION_NAME, NATIVE_FEATURE_MANAGEMENT_PACKAGE_EXTENSION_FILES, ) logger.info("Imunify360 native feature management disabled.") return True # disabled successfully defence360agent/feature_management/exceptions.py0000644000000000000000000000046200000000000017132 0ustar class FeatureManagementError(Exception): """Base exception for feature management""" class FeatureDisabledError(FeatureManagementError): """Feature is disabled for user""" class UserArgumentNotFound(FeatureManagementError): """Method/function lack the parameter which contains user name""" defence360agent/feature_management/hooks.py0000644000000000000000000000521500000000000016075 0ustar """ This module contains hook, which are called on feature management permission changes. Note that hooks are not executed automatically, developer is responsible to obtain specific hook using get_hook() function and call it. To add hook, create function with name equal to feature name """ import functools import logging from typing import Any, Callable, Optional from defence360agent.contracts.config import ConfigFile from defence360agent.feature_management.constants import ( AV, FULL, LOG, PROACTIVE, ) logger = logging.getLogger(__name__) def _hook_stub(*_): return True def _result_warn(callback): @functools.wraps(callback) def wrap(user, value): result = callback(user, value) result or logger.warning( "Hook '%s(%s)' failed for user '%s'.", callback.__name__, value, user, ) return result return wrap @_result_warn def antivirus(user: Optional[str], value: Any) -> bool: """Called when 'av' feature is changed""" if not user: return True config = ConfigFile() config_value = config.get("MALWARE_SCANNING", "default_action") user_config = ConfigFile(user) user_config_value = user_config.get("MALWARE_SCANNING", "default_action") if value == FULL: user_config_value = None elif ( user_config_value and user_config_value.startswith("cleanup") and config_value and config_value.startswith("cleanup") ): user_config_value = "notify" try: user_config.set( "MALWARE_SCANNING", "default_action", user_config_value ) except Exception: return False return True _PROACTIVE_MODE_BY_PERMISSION = { FULL: None, # inherit global PROACTIVE_DEFENCE.mode LOG: "LOG", # observe only, no enforcement # any other value (NA, legacy entries) maps to DISABLED below } @_result_warn def proactive(user: Optional[str], value: Any) -> bool: """Called when 'proactive' feature is changed""" if not user: return True # do nothing if no user specified config_value = _PROACTIVE_MODE_BY_PERMISSION.get(value, "DISABLED") try: ConfigFile(user).set("PROACTIVE_DEFENCE", "mode", config_value) except Exception: return False return True HOOKS = { AV: antivirus, PROACTIVE: proactive, } def get_hook(feature: str) -> Callable[[Optional[str], Any], bool]: """ Get hook for specific feature. If no hook is implemented for this feature, return stub function :param feature: feature name :return: callable hook """ return HOOKS.get(feature, _hook_stub) defence360agent/feature_management/lookup.py0000644000000000000000000000541700000000000016267 0ustar import inspect from typing import Any, Callable, List from ..contracts.config import MyImunifyConfig from ..contracts.permissions import is_plesk_service_plan_enabled from ..rpc_tools.lookup import wraps from .checkers import check_feature from .exceptions import UserArgumentNotFound features = set() # feature storage def _wrapper( name: str, permissions: List[str], func: Callable[..., Any], user_key: str ) -> Callable[..., Any]: """ Wrapper to enable feature management for func :param name: feature name :param func: function/method to wrap :param user_key: parameter name which contains user name :return: new callable object """ signature = inspect.signature(func) if user_key not in signature.parameters: raise UserArgumentNotFound( "Expecting argument '%s' for %s", user_key, func ) user_param = signature.parameters[user_key] user_key_required = user_param.default is user_param.empty def checker(**kwargs): if user_key_required and user_key not in kwargs: raise UserArgumentNotFound( "Argument '%s' for '%s' must be specified explicitly", user_key, func, ) if MyImunifyConfig.ENABLED: """Ignore the decorator if MyImunify is enabled""" return if is_plesk_service_plan_enabled(): """Ignore the decorator if Plesk service plan is enabled""" return user = kwargs.get(user_key, user_param.default) check_feature(name, permissions, user) @wraps(func) def wrapper(*args, **kwargs): checker(**kwargs) return func(*args, **kwargs) @wraps(func) async def async_wrapper(*args, **kwargs): checker(**kwargs) return await func(*args, **kwargs) if inspect.iscoroutinefunction(func): return async_wrapper return wrapper def feature( name: str, permissions: List[str], user_key="user" ) -> Callable[[Any], Any]: """ Get decorator to manage function/method with feature management :param name: feature name :param user_key: parameter name which contains user name :param permissions: list of permission values, with which user can access specifig endpoint :return: decorator """ def decorator(obj): if inspect.isclass(obj): for m_name, m_obj in getattr(obj, "__dict__", {}).items(): if not m_name.startswith("_") and inspect.isfunction(m_obj): wrapper = _wrapper(name, permissions, m_obj, user_key) setattr(obj, m_name, wrapper) elif inspect.isfunction(obj): obj = _wrapper(name, permissions, obj, user_key) features.add(name) return obj return decorator defence360agent/feature_management/model.py0000644000000000000000000000457400000000000016061 0ustar from peewee import CharField, Check, TextField from defence360agent.feature_management.constants import ( AV, AV_REPORT, FULL, LOG, NA, PROACTIVE, ) from defence360agent.model import Model, instance class FeatureManagementPerms(Model): """Permissions state for Feature Management. Each record/instance is a set of permissions of a single user. """ DEFAULT = "" class Meta: database = instance.db db_table = "feature_management_permissions" #: The username of the end-user, or an empty string for the default value #: for all new users. user = CharField(unique=True) #: How much the user can access and control Proactive Defense feature. #: Must be one of :obj:`.NA`, :obj:`.LOG` or :obj:`.FULL`. proactive = TextField( null=False, constraints=[ Check("proactive in ('{}','{}','{}')".format(NA, LOG, FULL)) ], default=FULL, ) #: How much the user can access and control Proactive Defense feature. #: Must be either :obj:`.NA` or :obj:`.AV_REPORT` or :obj:`.FULL`. av = TextField( null=False, constraints=[ Check("av in ('{}','{}','{}')".format(NA, AV_REPORT, FULL)) ], default=AV_REPORT, ) @classmethod def get_perm(cls, user: str) -> "FeatureManagementPerms": """ Get feature permissions by user name :param user: user name :return: :class:`FeatureManagementPerms` object for user """ default = cls.get_default() if user is None: return default defaults = { AV: default.av, PROACTIVE: default.proactive, } perm, _ = cls.get_or_create(user=user, defaults=defaults) return perm @classmethod def get_default(cls): """Get default permissions""" return cls.get(user=cls.DEFAULT) def is_default(self): """Check if current permission is default""" return self.user == self.DEFAULT def get_feature(self, key: str) -> str: """Get permission by feature name""" return getattr(self, key) def set_feature(self, key: str, value: str): """Set permission""" setattr(self, key, value) self.save() def as_dict(self): return { AV: self.av, PROACTIVE: self.proactive, } defence360agent/feature_management/plugins/0000755000000000000000000000000000000000000016056 5ustar defence360agent/feature_management/plugins/__init__.py0000644000000000000000000000000000000000000020155 0ustar defence360agent/feature_management/plugins/__pycache__/0000755000000000000000000000000000000000000020266 5ustar defence360agent/feature_management/plugins/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000032500000000000025466 0ustar r_jdS)Nrh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/__init__.pyrsrdefence360agent/feature_management/plugins/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000032500000000000024527 0ustar r_jdS)Nrh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/__init__.pyrsrdefence360agent/feature_management/plugins/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000001474600000000000025231 0ustar r_jJ ddlmZddlmZddlmZmZmZmZm Z ddl m Z ddl m Z ddlmZddlmZddlmZeeZGd d eZd S) ) getLogger)Dict)AVEXTENSION_DEFAULTSFEATURE_EXT_VARIABLESNATIVE_EXTENSION_NAME PROACTIVE$is_native_feature_management_enabled)FeatureManagementPerms set_feature)SettingsChangeBase)packagesc eZdZeegZfdZfdZdZe de de de e e ffdZ ede e e ffdZd Zd Zd ZxZS) %NativeFeatureManagementSettingsChangecxKt|d{V|dp|d}|rctd|t jt j|k dSdS)NuserusernamezResetting FM settings for %s) super_process_account_removedgetloggerinfor deletewhererexecute)selfmessager __class__s f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/native.pyrz>NativeFeatureManagementSettingsChange._process_account_removedsgg..w777777777{{6""=gkk*&=&=   KK6 = = = " ) + + 1 1&+t3  giiiii   c$Kt|d{Vd|jvr\|jd}tj|jtj|kdSdS)N old_username)r) r_process_modifydatar updaterrrr)rrr$r s r!r%z5NativeFeatureManagementSettingsChange._process_modify$sgg%%g......... W\ ) )"<7L " )w/? @ @ @ F F&+|;  giiiii * )r"cKi}|jD]3} |jt|}n#t$rd}YnwxYw|||<4|SN) FEATURES_LISTr&rKeyError)rrsettingsfeaturevalues r!_get_settings_from_messagez@NativeFeatureManagementSettingsChange._get_settings_from_message,sl) & &G  %:7%CD     %HW  s ( 77 package_nameadd_to_packagereturnclKtd| tj|d{V}nD#tj$r2td||cYSwxYwi} tjD]\}}||||<|S#t$rCtd||r#tj t|fitd{VYn/tj$rtd|YnwxYw|S)NzGetting package settings %szPackage %s doesn't existz,No extension's fields in package settings %s) rrrget_package_infoPackageNotExistErrorwarning_default_settingsritemsr+ add_extensionrr)clsr0r1pkg_infopackage_settingsr-pe_vars r!_get_package_settingsz;NativeFeatureManagementSettingsChange._get_package_settings6s  1<@@@ +%6|DDDDDDDDHH, + + + NN5| D D D((** * * * + E#8#>#@#@ = =,4V,< ))# #    KK>    ,)87I, E E E NN5| D D D D D E$$&&&s(:>A;:A;%B''A D3)DDc<dtjDS)Nc0i|]\}}|t|S)r).0r-r=s r! zKNativeFeatureManagementSettingsChange._default_settings..Ts4    '/   r")rr8rAr"r!r7z7NativeFeatureManagementSettingsChange._default_settingsRs,  #8#>#@#@    r"c8Kt|||d{VdSr)r )rrr-r.s r!on_settings_changez8NativeFeatureManagementSettingsChange.on_settings_changeYs0$///////////r"cdS)NTrA)rrs r!_message_is_relatablez;NativeFeatureManagementSettingsChange._message_is_relatable\str"c.Ktd{VSr)r )rs r! is_enabledz0NativeFeatureManagementSettingsChange.is_enabled_s$9;;;;;;;;;r")__name__ __module__ __qualname__r rr*rr%r/ classmethodstrboolrr> staticmethodr7rErGrI __classcell__)r s@r!rrsOM''04' c3h'''['6 tCH~   \ 000<<<<<<rZs4LKKKKK@@@@@@:99999 8  H<H<H<H<H<,>H<H<H<H<HNativeFeatureManagementSettingsChange._process_account_removedsgg..w777777777{{6""=gkk*&=&=   KK6 = = = " ) + + 1 1&+t3  giiiii   c$Kt|d{Vd|jvr\|jd}tj|jtj|kdSdS)N old_username)r) r_process_modifydatar updaterrrr)rrr$r s r!r%z5NativeFeatureManagementSettingsChange._process_modify$sgg%%g......... W\ ) )"<7L " )w/? @ @ @ F F&+|;  giiiii * )r"cKi}|jD]3} |jt|}n#t$rd}YnwxYw|||<4|SN) FEATURES_LISTr&rKeyError)rrsettingsfeaturevalues r!_get_settings_from_messagez@NativeFeatureManagementSettingsChange._get_settings_from_message,sl) & &G  %:7%CD     %HW  s ( 77 package_nameadd_to_packagereturnclKtd| tj|d{V}nD#tj$r2td||cYSwxYwi} tjD]\}}||||<|S#t$rCtd||r#tj t|fitd{VYn/tj$rtd|YnwxYw|S)NzGetting package settings %szPackage %s doesn't existz,No extension's fields in package settings %s) rrrget_package_infoPackageNotExistErrorwarning_default_settingsritemsr+ add_extensionrr)clsr0r1pkg_infopackage_settingsr-pe_vars r!_get_package_settingsz;NativeFeatureManagementSettingsChange._get_package_settings6s  1<@@@ +%6|DDDDDDDDHH, + + + NN5| D D D((** * * * + E#8#>#@#@ = =,4V,< ))# #    KK>    ,)87I, E E E NN5| D D D D D E$$&&&s(:>A;:A;%B''A D3)DDc<dtjDS)Nc0i|]\}}|t|S)r).0r-r=s r! zKNativeFeatureManagementSettingsChange._default_settings..Ts4    '/   r")rr8rAr"r!r7z7NativeFeatureManagementSettingsChange._default_settingsRs,  #8#>#@#@    r"c8Kt|||d{VdSr)r )rrr-r.s r!on_settings_changez8NativeFeatureManagementSettingsChange.on_settings_changeYs0$///////////r"cdS)NTrA)rrs r!_message_is_relatablez;NativeFeatureManagementSettingsChange._message_is_relatable\str"c.Ktd{VSr)r )rs r! is_enabledz0NativeFeatureManagementSettingsChange.is_enabled_s$9;;;;;;;;;r")__name__ __module__ __qualname__r rr*rr%r/ classmethodstrboolrr> staticmethodr7rErGrI __classcell__)r s@r!rrsOM''04' c3h'''['6 tCH~   \ 000<<<<<<rZs4LKKKKK@@@@@@:99999 8  H<H<H<H<H<,>H<H<H<H<HKt||j|_dS)N)r _migrate_migration_task)selfrs w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/proactive_log_migration.py create_sinkz!ProactiveLogMigration.create_sink!s' > $-  cKt|dd}||rdS| |d{VdS#tj$rYdSwxYw)Nr)getattrdonecancelasyncioCancelledError)rtasks rshutdownzProactiveLogMigration.shutdown,szt.55 <499;;< F  JJJJJJJJJ%    DD sA AAcvKtdd}|dkrdSdtjtjt ktjtjkzD}|sdSt dt|d}|D]T} t|ttd{Vr|dz }*#t$rtd|YQwxYwt d |t|dS) NFEATURE_MANAGEMENTproactive_disable_targetlogcg|] }|j S)user).0rows r z2ProactiveLogMigration._migrate..>s*     H    rz2Promoting %d proactive=na users to log (DEF-42523)rz-Failed to promote proactive=na user %s to logz%Promoted %d/%d users to proactive=log)rgetr selectwhere proactiverr&DEFAULTloggerinfolenr r r Exception exception)targetuserspromotedr&s rrzProactiveLogMigration._migrate6s|!! "<   U?? F   -466<<'1R7*/-56      F @ JJ     D $T9c::::::::"MH     CT   3  JJ     s>&C%%%D  D N) __name__ __module__ __qualname__r IM360SCOPErAbstractEventLooprr staticmethodrr%rrrrse KE  g&?     % % \% % % rr)__doc__rloggingr defence360agent.contracts.configr!defence360agent.contracts.pluginsrr,defence360agent.feature_management.constantsrrr (defence360agent.feature_management.modelr (defence360agent.feature_management.utilsr defence360agent.utilsr r r8r0rr%rrrGs   777777BBBBBBBBKKKKKKKKKKKKKKKK@@@@@@GGGGGGGG 8   A A A A A KA A  A A A rdefence360agent/feature_management/plugins/__pycache__/proactive_log_migration.cpython-311.pyc0000644000000000000000000001063700000000000027705 0ustar r_j dZddlZddlmZddlmZddlmZmZddl m Z m Z m Z ddl mZddlmZdd lmZmZeeZeGd d eZdS) a,Promote stale `proactive: na` perms to `log` when the deployment toggle ``FEATURE_MANAGEMENT.proactive_disable_target == "log"`` is set. DEF-42523. Without this, customers (notably Cloudways) flipping the toggle post-upgrade would only see the new behavior on users whose add-on state changes; pre-existing disabled users would linger in NA / mode=DISABLED indefinitely. Re-firing the proactive hook with LOG writes ``mode=LOG`` to user_config and updates the perm row. Idempotent: once promoted, no NA proactive perms remain, so subsequent boots no-op. N) getLogger) ConfigFile) MessageSinkthisguy)LOGNA PROACTIVE)FeatureManagementPerms) set_feature)Scopecreate_task_and_log_exceptionscNeZdZejZdejfdZdZ e dZ dS)ProactiveLogMigrationloopc>Kt||j|_dS)N)r _migrate_migration_task)selfrs w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/plugins/proactive_log_migration.py create_sinkz!ProactiveLogMigration.create_sink!s' > $-  cKt|dd}||rdS| |d{VdS#tj$rYdSwxYw)Nr)getattrdonecancelasyncioCancelledError)rtasks rshutdownzProactiveLogMigration.shutdown,szt.55 <499;;< F  JJJJJJJJJ%    DD sA AAcvKtdd}|dkrdSdtjtjt ktjtjkzD}|sdSt dt|d}|D]T} t|ttd{Vr|dz }*#t$rtd|YQwxYwt d |t|dS) NFEATURE_MANAGEMENTproactive_disable_targetlogcg|] }|j S)user).0rows r z2ProactiveLogMigration._migrate..>s*     H    rz2Promoting %d proactive=na users to log (DEF-42523)rz-Failed to promote proactive=na user %s to logz%Promoted %d/%d users to proactive=log)rgetr selectwhere proactiverr&DEFAULTloggerinfolenr r r Exception exception)targetuserspromotedr&s rrzProactiveLogMigration._migrate6s|!! "<   U?? F   -466<<'1R7*/-56      F @ JJ     D $T9c::::::::"MH     CT   3  JJ     s>&C%%%D  D N) __name__ __module__ __qualname__r IM360SCOPErAbstractEventLooprr staticmethodrr%rrrrse KE  g&?     % % \% % % rr)__doc__rloggingr defence360agent.contracts.configr!defence360agent.contracts.pluginsrr,defence360agent.feature_management.constantsrrr (defence360agent.feature_management.modelr (defence360agent.feature_management.utilsr defence360agent.utilsr r r8r0rr%rrrGs   777777BBBBBBBBKKKKKKKKKKKKKKKK@@@@@@GGGGGGGG 8   A A A A A KA A  A A A rdefence360agent/feature_management/plugins/native.py0000644000000000000000000000651200000000000017722 0ustar from logging import getLogger from typing import Dict from defence360agent.feature_management.constants import ( AV, EXTENSION_DEFAULTS, FEATURE_EXT_VARIABLES, NATIVE_EXTENSION_NAME, PROACTIVE, ) from defence360agent.feature_management.control import ( is_native_feature_management_enabled, ) from defence360agent.feature_management.model import FeatureManagementPerms from defence360agent.feature_management.utils import set_feature from defence360agent.plugins.event_monitor_message_processor import ( SettingsChangeBase, ) from defence360agent.subsys.panels.cpanel import packages logger = getLogger(__name__) class NativeFeatureManagementSettingsChange(SettingsChangeBase): FEATURES_LIST = [PROACTIVE, AV] async def _process_account_removed(self, message): await super()._process_account_removed(message) user = message.get("user") or message.get("username") if user: logger.info("Resetting FM settings for %s", user) FeatureManagementPerms.delete().where( FeatureManagementPerms.user == user ).execute() async def _process_modify(self, message): await super()._process_modify(message) if "old_username" in message.data: # User renamed old_username = message.data["old_username"] FeatureManagementPerms.update(user=message.username).where( FeatureManagementPerms.user == old_username ).execute() async def _get_settings_from_message(self, message): settings = {} for feature in self.FEATURES_LIST: try: value = message.data[FEATURE_EXT_VARIABLES[feature]] except KeyError: value = None settings[feature] = value return settings @classmethod async def _get_package_settings( cls, package_name: str, add_to_package: bool ) -> Dict[str, str]: logger.info("Getting package settings %s", package_name) try: pkg_info = await packages.get_package_info(package_name) except packages.PackageNotExistError: logger.warning("Package %s doesn't exist", package_name) return cls._default_settings() package_settings = {} try: for feature, pe_var in FEATURE_EXT_VARIABLES.items(): package_settings[feature] = pkg_info[pe_var] return package_settings except KeyError: logger.info( "No extension's fields in package settings %s", pkg_info ) if add_to_package: await packages.add_extension( NATIVE_EXTENSION_NAME, pkg_info, **EXTENSION_DEFAULTS ) except packages.PackageNotExistError: logger.warning("Package %s doesn't exist", package_name) return cls._default_settings() @staticmethod def _default_settings() -> Dict[str, str]: return { feature: EXTENSION_DEFAULTS[pe_var] for feature, pe_var in FEATURE_EXT_VARIABLES.items() } async def on_settings_change(self, user, feature, value): await set_feature(user, feature, value) def _message_is_relatable(self, message): return True async def is_enabled(self): return await is_native_feature_management_enabled() defence360agent/feature_management/plugins/proactive_log_migration.py0000644000000000000000000000632600000000000023345 0ustar """Promote stale `proactive: na` perms to `log` when the deployment toggle ``FEATURE_MANAGEMENT.proactive_disable_target == "log"`` is set. DEF-42523. Without this, customers (notably Cloudways) flipping the toggle post-upgrade would only see the new behavior on users whose add-on state changes; pre-existing disabled users would linger in NA / mode=DISABLED indefinitely. Re-firing the proactive hook with LOG writes ``mode=LOG`` to user_config and updates the perm row. Idempotent: once promoted, no NA proactive perms remain, so subsequent boots no-op. """ import asyncio from logging import getLogger from defence360agent.contracts.config import ConfigFile from defence360agent.contracts.plugins import MessageSink, thisguy from defence360agent.feature_management.constants import LOG, NA, PROACTIVE from defence360agent.feature_management.model import FeatureManagementPerms from defence360agent.feature_management.utils import set_feature from defence360agent.utils import Scope, create_task_and_log_exceptions logger = getLogger(__name__) @thisguy class ProactiveLogMigration(MessageSink): # Proactive Defence is an Imunify360-only feature. ImunifyAV-only # installs have no proactive permissions to migrate and no # FEATURE_MANAGEMENT.proactive_disable_target schema key. SCOPE = Scope.IM360 async def create_sink(self, loop: asyncio.AbstractEventLoop): # Spawn the migration as a background task so the agent's other # MessageSinks (and serving traffic) come up immediately. The # migration is idempotent on retry, so cancellation at shutdown # is safe. create_task_and_log_exceptions surfaces failures # to the agent's exception handler instead of silently dropping # them (the bare loop.create_task would). self._migration_task = create_task_and_log_exceptions( loop, self._migrate ) async def shutdown(self): task = getattr(self, "_migration_task", None) if task is None or task.done(): return task.cancel() try: await task except asyncio.CancelledError: pass @staticmethod async def _migrate(): target = ConfigFile().get( "FEATURE_MANAGEMENT", "proactive_disable_target" ) if target != "log": return users = [ row.user for row in FeatureManagementPerms.select().where( (FeatureManagementPerms.proactive == NA) & ( FeatureManagementPerms.user != FeatureManagementPerms.DEFAULT ) ) ] if not users: return logger.info( "Promoting %d proactive=na users to log (DEF-42523)", len(users), ) promoted = 0 for user in users: try: if await set_feature(user, PROACTIVE, LOG): promoted += 1 except Exception: logger.exception( "Failed to promote proactive=na user %s to log", user ) logger.info( "Promoted %d/%d users to proactive=log", promoted, len(users), ) defence360agent/feature_management/rpc/0000755000000000000000000000000000000000000015161 5ustar defence360agent/feature_management/rpc/__init__.py0000644000000000000000000000000000000000000017260 0ustar defence360agent/feature_management/rpc/__pycache__/0000755000000000000000000000000000000000000017371 5ustar defence360agent/feature_management/rpc/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000032100000000000024565 0ustar r_jdS)Nrd/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/__init__.pyrsrdefence360agent/feature_management/rpc/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000032100000000000023626 0ustar r_jdS)Nrd/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/__init__.pyrsrdefence360agent/feature_management/rpc/endpoints/0000755000000000000000000000000000000000000017164 5ustar defence360agent/feature_management/rpc/endpoints/__init__.py0000644000000000000000000000011300000000000021270 0ustar from . import native, show, update __all__ = ["native", "show", "update"] defence360agent/feature_management/rpc/endpoints/__pycache__/0000755000000000000000000000000000000000000021374 5ustar defence360agent/feature_management/rpc/endpoints/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000051600000000000026576 0ustar r_jK"ddlmZmZmZgdZdS))nativeshowupdateN)rrr__all__n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/__init__.pyr s2"""""""""" & & &r defence360agent/feature_management/rpc/endpoints/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000051600000000000025637 0ustar r_jK"ddlmZmZmZgdZdS))nativeshowupdateN)rrr__all__n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/__init__.pyr s2"""""""""" & & &r defence360agent/feature_management/rpc/endpoints/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000000556300000000000026334 0ustar r_j|ddlmZddlmZddlmZmZddlmZm Z m Z m Z ee Z gZGddeZdS) ) getLogger)MyImunifyConfig) RootEndpointsbind)!disable_native_feature_management enable_native_feature_management$is_native_feature_management_enabled&is_native_feature_management_supportedceZdZfdZeddddZeddddZedddd ZxZS) FeatureManagementNativeEndpointscJt|dSN)super__init__)selfsink __class__s l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/native.pyrz)FeatureManagementNativeEndpoints.__init__s! zfeature-managementnativestatuscKtjsCttd{V}tt d{V}nd}d}d||diS)NFitems) supportedenabled)rENABLEDboolr r )rrrs r feature_management_native_statuszAFeatureManagementNativeEndpoints.feature_management_native_statuss& #I#K#KKKKKKKLLI!E!G!GGGGGGGHHGGIG &"  renablec2Ktd{VdSr)r )rs r feature_management_native_enablezAFeatureManagementNativeEndpoints.feature_management_native_enable&s*.00000000000rdisablec>Ktd{V}|rddiSdS)NrzAImunify360 package extensions have been removed from all packages)r)rdisableds r!feature_management_native_disablezBFeatureManagementNativeEndpoints.feature_management_native_disable*sE:<<<<<<<<  \   r) __name__ __module__ __qualname__rrrr"r& __classcell__)rs@rr r s T (33   43   T (3311431 T )4454rr N)loggingr defence360agent.contracts.configr defence360agent.rpc_tools.lookuprrcontrolrr r r r'logger__all__r rrr2s<<<<<<@@@@@@@@ 8   }rdefence360agent/feature_management/rpc/endpoints/__pycache__/native.cpython-311.pyc0000644000000000000000000000556300000000000025375 0ustar r_j|ddlmZddlmZddlmZmZddlmZm Z m Z m Z ee Z gZGddeZdS) ) getLogger)MyImunifyConfig) RootEndpointsbind)!disable_native_feature_management enable_native_feature_management$is_native_feature_management_enabled&is_native_feature_management_supportedceZdZfdZeddddZeddddZedddd ZxZS) FeatureManagementNativeEndpointscJt|dSN)super__init__)selfsink __class__s l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/native.pyrz)FeatureManagementNativeEndpoints.__init__s! zfeature-managementnativestatuscKtjsCttd{V}tt d{V}nd}d}d||diS)NFitems) supportedenabled)rENABLEDboolr r )rrrs r feature_management_native_statuszAFeatureManagementNativeEndpoints.feature_management_native_statuss& #I#K#KKKKKKKLLI!E!G!GGGGGGGHHGGIG &"  renablec2Ktd{VdSr)r )rs r feature_management_native_enablezAFeatureManagementNativeEndpoints.feature_management_native_enable&s*.00000000000rdisablec>Ktd{V}|rddiSdS)NrzAImunify360 package extensions have been removed from all packages)r)rdisableds r!feature_management_native_disablezBFeatureManagementNativeEndpoints.feature_management_native_disable*sE:<<<<<<<<  \   r) __name__ __module__ __qualname__rrrr"r& __classcell__)rs@rr r s T (33   43   T (3311431 T )4454rr N)loggingr defence360agent.contracts.configr defence360agent.rpc_tools.lookuprrcontrolrr r r r'logger__all__r rrr2s<<<<<<@@@@@@@@ 8   }rdefence360agent/feature_management/rpc/endpoints/__pycache__/show.cpython-311.opt-1.pyc0000644000000000000000000001315400000000000026021 0ustar r_j3 ddlmZddlmcmcmZddlmZm Z m Z ddl m Z ddl mZddlmZddlmZddlmZmZmZeeZeGd d eZGd d eZdS) ) getLoggerN)AVFULL PROACTIVE)features)FeatureManagementPerms)builtin_feature_management_only)apply_order_by)CommonEndpoints RootEndpointsbindceZdZdZedddZedddZedd d d ZdS) "FeatureManagementShowRootEndpointscPttfD]}||tk||<|SN)rrr)selfitemfeatures j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/show.py _adapt_valuez/FeatureManagementShowRootEndpoints._adapt_values.9} 2 2G MT1DMM feature-managementlistc2KdttiS)zGet list of featuresitems)rr)rs rfeature_management_listz:FeatureManagementShowRootEndpoints.feature_management_listsh((rdefaultsc~Ktj}d||iS)zGet default feature permissionsr)r get_defaultras_dict)rperms rfeature_management_defaultsz>FeatureManagementShowRootEndpoints.feature_management_defaults s7&133**4<<>>::;;rshowNc Ktjd{V r fd D t j}|rt |t|} fd|D}t|}|r ||d}|r |d|} fd|D}||fS)zShow permissionsNcdi|],\}}|vs ttfd|)||-S)c |vSr)xsearchs rzWFeatureManagementShowRootEndpoints.feature_management_show...0s v{r)anymap).0userdomainsr)s r zNFeatureManagementShowRootEndpoints.feature_management_show..-sP!D'T>>S-B-B-B-BG)L)L%M%M>g!>>rc&g|] }|jv |Sr')r.)r-r!userss r zNFeatureManagementShowRootEndpoints.feature_management_show..6s%:::$tyE'9'9'9'9'9rcg|]=}|j|j|d>S))namer/r)r.rr )r-r!rr2s rr3zNFeatureManagementShowRootEndpoints.feature_management_show..<sZ      + --dllnn==     r)hp HostingPanelget_domains_per_userrrselectr len) rr)limitoffsetorder_byqperms perms_lenresultr2s `` @rfeature_management_showz:FeatureManagementShowRootEndpoints.feature_management_show&s# o''<<>>>>>>>>  %*[[]]E # ) + +  Dx)?CCA::::!:::JJ  #&''NE  "&5&ME         &  r)NNNN)__name__ __module__ __qualname__rr rr"rBr'rrrrs  T ''))(') T  ++<<,+<  T ''=A!!!('!!!rrcJeZdZeeddddZdS)!FeatureManagementShowAnyEndpointsrgetNcZKtj|}d|iS)zGet user feature permissionsr)rget_permr )r.r!s rfeature_management_getz8FeatureManagementShowAnyEndpoints.feature_management_getIs+&.t44((rr)rCrDrE staticmethodr rKr'rrrGrGHsJ T &&)))'&\)))rrG)loggingr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr6,defence360agent.feature_management.constantsrrr)defence360agent.feature_management.lookupr(defence360agent.feature_management.modelr6defence360agent.feature_management.rpc.endpoints.utilsr $defence360agent.model.simplificationr defence360agent.rpc_tools.lookupr r r rCloggerrrGr'rrrYs_888888888888LLLLLLLLLL>>>>>>KKKKKK@????? 8  !0!0!0!0!0!0!0!! 0!f))))))))))rdefence360agent/feature_management/rpc/endpoints/__pycache__/show.cpython-311.pyc0000644000000000000000000001315400000000000025062 0ustar r_j3 ddlmZddlmcmcmZddlmZm Z m Z ddl m Z ddl mZddlmZddlmZddlmZmZmZeeZeGd d eZGd d eZdS) ) getLoggerN)AVFULL PROACTIVE)features)FeatureManagementPerms)builtin_feature_management_only)apply_order_by)CommonEndpoints RootEndpointsbindceZdZdZedddZedddZedd d d ZdS) "FeatureManagementShowRootEndpointscPttfD]}||tk||<|SN)rrr)selfitemfeatures j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/show.py _adapt_valuez/FeatureManagementShowRootEndpoints._adapt_values.9} 2 2G MT1DMM feature-managementlistc2KdttiS)zGet list of featuresitems)rr)rs rfeature_management_listz:FeatureManagementShowRootEndpoints.feature_management_listsh((rdefaultsc~Ktj}d||iS)zGet default feature permissionsr)r get_defaultras_dict)rperms rfeature_management_defaultsz>FeatureManagementShowRootEndpoints.feature_management_defaults s7&133**4<<>>::;;rshowNc Ktjd{V r fd D t j}|rt |t|} fd|D}t|}|r ||d}|r |d|} fd|D}||fS)zShow permissionsNcdi|],\}}|vs ttfd|)||-S)c |vSr)xsearchs rzWFeatureManagementShowRootEndpoints.feature_management_show...0s v{r)anymap).0userdomainsr)s r zNFeatureManagementShowRootEndpoints.feature_management_show..-sP!D'T>>S-B-B-B-BG)L)L%M%M>g!>>rc&g|] }|jv |Sr')r.)r-r!userss r zNFeatureManagementShowRootEndpoints.feature_management_show..6s%:::$tyE'9'9'9'9'9rcg|]=}|j|j|d>S))namer/r)r.rr )r-r!rr2s rr3zNFeatureManagementShowRootEndpoints.feature_management_show..<sZ      + --dllnn==     r)hp HostingPanelget_domains_per_userrrselectr len) rr)limitoffsetorder_byqperms perms_lenresultr2s `` @rfeature_management_showz:FeatureManagementShowRootEndpoints.feature_management_show&s# o''<<>>>>>>>>  %*[[]]E # ) + +  Dx)?CCA::::!:::JJ  #&''NE  "&5&ME         &  r)NNNN)__name__ __module__ __qualname__rr rr"rBr'rrrrs  T ''))(') T  ++<<,+<  T ''=A!!!('!!!rrcJeZdZeeddddZdS)!FeatureManagementShowAnyEndpointsrgetNcZKtj|}d|iS)zGet user feature permissionsr)rget_permr )r.r!s rfeature_management_getz8FeatureManagementShowAnyEndpoints.feature_management_getIs+&.t44((rr)rCrDrE staticmethodr rKr'rrrGrGHsJ T &&)))'&\)))rrG)loggingr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr6,defence360agent.feature_management.constantsrrr)defence360agent.feature_management.lookupr(defence360agent.feature_management.modelr6defence360agent.feature_management.rpc.endpoints.utilsr $defence360agent.model.simplificationr defence360agent.rpc_tools.lookupr r r rCloggerrrGr'rrrYs_888888888888LLLLLLLLLL>>>>>>KKKKKK@????? 8  !0!0!0!0!0!0!0!! 0!f))))))))))rdefence360agent/feature_management/rpc/endpoints/__pycache__/update.cpython-311.opt-1.pyc0000644000000000000000000001026200000000000026320 0ustar r_jddlmZddlmZmZddlmcmcmZ ddl m Z ddl m Z mZmZmZmZmZddlmZddlmZmZddlmZmZeeZd efd ZeGd d eZdS) ) getLogger)AnyListN) ConfigFile)AV AV_REPORTFULLLOGNA PROACTIVE)builtin_feature_management_only)update_default update_users) RootEndpointsbindreturncntdd}|dkrtntS)zReturn the permission value that "disable proactive" should resolve to. Reads FEATURE_MANAGEMENT.proactive_disable_target. Default "na" preserves legacy behavior; "log" gives observe-only PROACTIVE_DEFENCE instead of fully off. FEATURE_MANAGEMENTproactive_disable_targetlog)rgetr r )values l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/update.py_proactive_disable_targetrs1 LL  13M N NE5..33b(ceZdZededeedefdZdZe ddd defd Z e dd d defd Z dS) FeatureManagementUpdateEndpointsfeatureusersrcK|sdt||d{VrdndiSdt|||tjd{Vd{ViS)Nitemssucceedfailed)rrhp HostingPanel get_users)rrrs r_updatez(FeatureManagementUpdateEndpoints._update's '77777777  <R_->->-H-H-J-J'J'J'J'J'J'J  rcx|rtS|tkrtS|tkrt St SN)r rrr rr )selfrrs r _adapt_valuez-FeatureManagementUpdateEndpoints._adapt_value5s:  K b==  i  ,.. . rzfeature-managementenableNchK|||||dd{VS)zEnable specified featureTNr'r+r*rrs rfeature_management_enablez:FeatureManagementUpdateEndpoints.feature_management_enable>sS\\ UD--gt<<         rdisablechK|||||dd{VS)zDisable specified featureFNr.r/s rfeature_management_disablez;FeatureManagementUpdateEndpoints.feature_management_disableEsS\\ UD--gu==         rr)) __name__ __module__ __qualname__ staticmethodstrrrr'r+rr0r3rrrr%s  s  49  S    \   T ))  s   *)  T  **     +*   rr) loggingrtypingrr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr$ defence360agent.contracts.configr,defence360agent.feature_management.constantsrrr r r r 6defence360agent.feature_management.rpc.endpoints.utilsr (defence360agent.feature_management.utilsrr defence360agent.rpc_tools.lookuprrr4loggerr8rrr9rrrFs888888888888777777A@@@@@@@ 8  )3))))!$ $ $ $ $ }$ $ ! $ $ $ rdefence360agent/feature_management/rpc/endpoints/__pycache__/update.cpython-311.pyc0000644000000000000000000001026200000000000025361 0ustar r_jddlmZddlmZmZddlmcmcmZ ddl m Z ddl m Z mZmZmZmZmZddlmZddlmZmZddlmZmZeeZd efd ZeGd d eZdS) ) getLogger)AnyListN) ConfigFile)AV AV_REPORTFULLLOGNA PROACTIVE)builtin_feature_management_only)update_default update_users) RootEndpointsbindreturncntdd}|dkrtntS)zReturn the permission value that "disable proactive" should resolve to. Reads FEATURE_MANAGEMENT.proactive_disable_target. Default "na" preserves legacy behavior; "log" gives observe-only PROACTIVE_DEFENCE instead of fully off. FEATURE_MANAGEMENTproactive_disable_targetlog)rgetr r )values l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/update.py_proactive_disable_targetrs1 LL  13M N NE5..33b(ceZdZededeedefdZdZe ddd defd Z e dd d defd Z dS) FeatureManagementUpdateEndpointsfeatureusersrcK|sdt||d{VrdndiSdt|||tjd{Vd{ViS)Nitemssucceedfailed)rrhp HostingPanel get_users)rrrs r_updatez(FeatureManagementUpdateEndpoints._update's '77777777  <R_->->-H-H-J-J'J'J'J'J'J'J  rcx|rtS|tkrtS|tkrt St SN)r rrr rr )selfrrs r _adapt_valuez-FeatureManagementUpdateEndpoints._adapt_value5s:  K b==  i  ,.. . rzfeature-managementenableNchK|||||dd{VS)zEnable specified featureTNr'r+r*rrs rfeature_management_enablez:FeatureManagementUpdateEndpoints.feature_management_enable>sS\\ UD--gt<<         rdisablechK|||||dd{VS)zDisable specified featureFNr.r/s rfeature_management_disablez;FeatureManagementUpdateEndpoints.feature_management_disableEsS\\ UD--gu==         rr)) __name__ __module__ __qualname__ staticmethodstrrrr'r+rr0r3rrrr%s  s  49  S    \   T ))  s   *)  T  **     +*   rr) loggingrtypingrr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr$ defence360agent.contracts.configr,defence360agent.feature_management.constantsrrr r r r 6defence360agent.feature_management.rpc.endpoints.utilsr (defence360agent.feature_management.utilsrr defence360agent.rpc_tools.lookuprrr4loggerr8rrr9rrrFs888888888888777777A@@@@@@@ 8  )3))))!$ $ $ $ $ }$ $ ! $ $ $ rdefence360agent/feature_management/rpc/endpoints/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000000427200000000000026202 0ustar r_j8ddlZddlmZddlmZddlmZdZdS)N)$is_native_feature_management_enabledwraps)ValidationErrorc$d}tj|s Jdt|diD]J\}}|ds0tj|r||}t |||K|S)z This decorator is intended to wrap rpc endpoint classes. It will throw ValidationError if native feature management is enabled when any of decorated class methods is called c<tfd}|S)NcjKtd{Vrtd|i|d{VS)Nz|Command is disabled because native feature management is enabled. Please use your hosting panel interface to manage features)rr)argskwargscoros k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/utils.pywrapperzBbuiltin_feature_management_only.._wrapper..wrappersg9;;;;;;;; %* t.v........ .r)r rs` r _wrapperz1builtin_feature_management_only.._wrappers3 t / / / /  /rz+This decorator can only be used for classes__dict___)inspectisclassgetattritems startswithiscoroutinefunctionsetattr)clsrm_namem_objwrappeds r builtin_feature_management_onlyr s    ?3  NN!NNN  j"55;;==**   %% *'*Ee*L*L *huooG C ) ) ) Jr)r*defence360agent.feature_management.controlr defence360agent.rpc_tools.lookupr"defence360agent.rpc_tools.validaterrrr r#sk322222>>>>>>rdefence360agent/feature_management/rpc/endpoints/__pycache__/utils.cpython-311.pyc0000644000000000000000000000427200000000000025243 0ustar r_j8ddlZddlmZddlmZddlmZdZdS)N)$is_native_feature_management_enabledwraps)ValidationErrorc$d}tj|s Jdt|diD]J\}}|ds0tj|r||}t |||K|S)z This decorator is intended to wrap rpc endpoint classes. It will throw ValidationError if native feature management is enabled when any of decorated class methods is called c<tfd}|S)NcjKtd{Vrtd|i|d{VS)Nz|Command is disabled because native feature management is enabled. Please use your hosting panel interface to manage features)rr)argskwargscoros k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/feature_management/rpc/endpoints/utils.pywrapperzBbuiltin_feature_management_only.._wrapper..wrappersg9;;;;;;;; %* t.v........ .r)r rs` r _wrapperz1builtin_feature_management_only.._wrappers3 t / / / /  /rz+This decorator can only be used for classes__dict___)inspectisclassgetattritems startswithiscoroutinefunctionsetattr)clsrm_namem_objwrappeds r builtin_feature_management_onlyr s    ?3  NN!NNN  j"55;;==**   %% *'*Ee*L*L *huooG C ) ) ) Jr)r*defence360agent.feature_management.controlr defence360agent.rpc_tools.lookupr"defence360agent.rpc_tools.validaterrrr r#sk322222>>>>>>rdefence360agent/feature_management/rpc/endpoints/native.py0000644000000000000000000000274500000000000021034 0ustar from logging import getLogger from defence360agent.contracts.config import MyImunifyConfig from defence360agent.rpc_tools.lookup import RootEndpoints, bind from ...control import ( disable_native_feature_management, enable_native_feature_management, is_native_feature_management_enabled, is_native_feature_management_supported, ) logger = getLogger(__name__) __all__ = [] class FeatureManagementNativeEndpoints(RootEndpoints): def __init__(self, sink): super().__init__(sink) @bind("feature-management", "native", "status") async def feature_management_native_status(self): if not MyImunifyConfig.ENABLED: supported = bool(await is_native_feature_management_supported()) enabled = bool(await is_native_feature_management_enabled()) else: supported = False enabled = False return { "items": { "supported": supported, "enabled": enabled, } } @bind("feature-management", "native", "enable") async def feature_management_native_enable(self): await enable_native_feature_management() @bind("feature-management", "native", "disable") async def feature_management_native_disable(self): disabled = await disable_native_feature_management() if disabled: return { "items": "Imunify360 package extensions have been removed from all packages" # noqa: E501 } defence360agent/feature_management/rpc/endpoints/show.py0000644000000000000000000000506300000000000020522 0ustar from logging import getLogger import defence360agent.subsys.panels.hosting_panel as hp from defence360agent.feature_management.constants import AV, FULL, PROACTIVE from defence360agent.feature_management.lookup import features from defence360agent.feature_management.model import FeatureManagementPerms from defence360agent.feature_management.rpc.endpoints.utils import ( builtin_feature_management_only, ) from defence360agent.model.simplification import apply_order_by from defence360agent.rpc_tools.lookup import ( CommonEndpoints, RootEndpoints, bind, ) logger = getLogger(__name__) @builtin_feature_management_only class FeatureManagementShowRootEndpoints(RootEndpoints): def _adapt_value(self, item): for feature in AV, PROACTIVE: item[feature] = item[feature] == FULL return item @bind("feature-management", "list") async def feature_management_list(self): """Get list of features""" return {"items": list(features)} @bind("feature-management", "defaults") async def feature_management_defaults(self): """Get default feature permissions""" perm = FeatureManagementPerms.get_default() return {"items": self._adapt_value(perm.as_dict())} @bind("feature-management", "show") async def feature_management_show( self, search=None, limit=None, offset=None, order_by=None ): """Show permissions""" users = await hp.HostingPanel().get_domains_per_user() if search: users = { user: domains for user, domains in users.items() if search in user or any(map(lambda x: search in x, domains)) } q = FeatureManagementPerms.select() if order_by: q = apply_order_by(order_by, FeatureManagementPerms, q) perms = [perm for perm in q if perm.user in users] perms_len = len(perms) if offset: perms = perms[offset:] if limit: perms = perms[:limit] result = [ { "name": perm.user, "domains": users[perm.user], "features": self._adapt_value(perm.as_dict()), } for perm in perms ] return perms_len, result class FeatureManagementShowAnyEndpoints(CommonEndpoints): @staticmethod @bind("feature-management", "get") async def feature_management_get(user=None): """Get user feature permissions""" perm = FeatureManagementPerms.get_perm(user) return {"items": perm.as_dict()} defence360agent/feature_management/rpc/endpoints/update.py0000644000000000000000000000436300000000000021026 0ustar from logging import getLogger from typing import Any, List import defence360agent.subsys.panels.hosting_panel as hp from defence360agent.contracts.config import ConfigFile from defence360agent.feature_management.constants import ( AV, AV_REPORT, FULL, LOG, NA, PROACTIVE, ) from defence360agent.feature_management.rpc.endpoints.utils import ( builtin_feature_management_only, ) from defence360agent.feature_management.utils import ( update_default, update_users, ) from defence360agent.rpc_tools.lookup import RootEndpoints, bind logger = getLogger(__name__) def _proactive_disable_target() -> str: """Return the permission value that "disable proactive" should resolve to. Reads FEATURE_MANAGEMENT.proactive_disable_target. Default "na" preserves legacy behavior; "log" gives observe-only PROACTIVE_DEFENCE instead of fully off. """ value = ConfigFile().get("FEATURE_MANAGEMENT", "proactive_disable_target") return LOG if value == "log" else NA @builtin_feature_management_only class FeatureManagementUpdateEndpoints(RootEndpoints): @staticmethod async def _update(feature: str, users: List[str], value: Any): if not users: return { "items": "succeed" if await update_default(feature, value) else "failed" } return { "items": await update_users( feature, users, value, await hp.HostingPanel().get_users() ) } def _adapt_value(self, feature, value): if value: return FULL if feature == AV: return AV_REPORT if feature == PROACTIVE: return _proactive_disable_target() return NA @bind("feature-management", "enable") async def feature_management_enable(self, feature: str, users=None): """Enable specified feature""" return await self._update( feature, users, self._adapt_value(feature, True) ) @bind("feature-management", "disable") async def feature_management_disable(self, feature: str, users=None): """Disable specified feature""" return await self._update( feature, users, self._adapt_value(feature, False) ) defence360agent/feature_management/rpc/endpoints/utils.py0000644000000000000000000000241000000000000020673 0ustar import inspect from defence360agent.feature_management.control import ( is_native_feature_management_enabled, ) from defence360agent.rpc_tools.lookup import wraps from defence360agent.rpc_tools.validate import ValidationError def builtin_feature_management_only(cls): """ This decorator is intended to wrap rpc endpoint classes. It will throw ValidationError if native feature management is enabled when any of decorated class methods is called """ def _wrapper(coro): @wraps(coro) async def wrapper(*args, **kwargs): if await is_native_feature_management_enabled(): raise ValidationError( "Command is disabled because native " "feature management is enabled. " "Please use your hosting panel interface" " to manage features" ) return await coro(*args, **kwargs) return wrapper assert inspect.isclass(cls), "This decorator can only be used for classes" for m_name, m_obj in getattr(cls, "__dict__", {}).items(): if not m_name.startswith("_") and inspect.iscoroutinefunction(m_obj): wrapped = _wrapper(m_obj) setattr(cls, m_name, wrapped) return cls defence360agent/feature_management/rpc/schema/0000755000000000000000000000000000000000000016421 5ustar defence360agent/feature_management/rpc/schema/native.pickle0000644000000000000000000000066000000000000021102 0ustar }( feature-management native enable}( return_typeNullAgentResponsehelp (internal)cli}(users]roota require_rpcanyuu!feature-management native disable}(help (internal)cli}(users]roota require_rpcanyuu feature-management native status}( return_type+FeaturesManagementNativeStatusAgentResponsehelp (internal)cli}(users]roota require_rpcanyuuu.defence360agent/feature_management/rpc/schema/native.yaml0000644000000000000000000000102500000000000020571 0ustar # enables native feature-management feature-management native enable: return_type: NullAgentResponse help: (internal) cli: users: - root require_rpc: any feature-management native disable: help: (internal) cli: users: - root require_rpc: any # checks native feature-management status # (enabled/disabled supported/not supported) feature-management native status: return_type: FeaturesManagementNativeStatusAgentResponse help: (internal) cli: users: - root require_rpc: any defence360agent/feature_management/rpc/schema/show.pickle0000644000000000000000000000237200000000000020576 0ustar }(feature-management list}( return_type#FeaturesManagementListAgentResponsehelpList all available featurestypedictcli}users]rootasufeature-management defaults}( return_type'FeaturesManagementDefaultsAgentResponsehelp3Get the default state of all features for new userstypedictcli}users]rootasufeature-management show}( return_type#FeaturesManagementShowAgentResponsehelp,List the state of all features for all userstypedictcli}users]rootasschema}(search}(helpSearch specific users by name.typestringnullableulimit}(help5Limits the output with specified number of incidents.typeintegercoerceintdefaultKduoffset}(helpOffset for pagination.typeintegercoerceintdefaultKuorder_by}(help&List of fields to sort the results by.typelistschema}(typeorder_bycoerceorder_byunullableuuufeature-management get}( return_type"FeaturesManagementGetAgentResponsehelp1Get the state of all features for a specific usertypedictcli}users]rootasschema}user}(help:Specifies a user name to obtain the status of features fortypestringusuu.defence360agent/feature_management/rpc/schema/show.yaml0000644000000000000000000000244000000000000020265 0ustar feature-management list: return_type: FeaturesManagementListAgentResponse help: List all available features type: dict cli: users: - root feature-management defaults: return_type: FeaturesManagementDefaultsAgentResponse help: Get the default state of all features for new users type: dict cli: users: - root feature-management show: return_type: FeaturesManagementShowAgentResponse help: List the state of all features for all users type: dict cli: users: - root schema: search: help: Search specific users by name. type: string nullable: true limit: help: Limits the output with specified number of incidents. type: integer coerce: int default: 100 offset: help: Offset for pagination. type: integer coerce: int default: 0 order_by: help: List of fields to sort the results by. type: list schema: type: order_by coerce: order_by nullable: true feature-management get: return_type: FeaturesManagementGetAgentResponse help: Get the state of all features for a specific user type: dict cli: users: - root schema: user: help: Specifies a user name to obtain the status of features for type: string defence360agent/feature_management/rpc/schema/update.pickle0000644000000000000000000000217600000000000021102 0ustar s}(feature-management enable}( return_type#FeaturesManagementEditAgentResponsehelpCEnable a feature for specified users or all new ones (set defaults)typedictcli}users]rootasschema}(feature}(helpKAllowed values: `av` for Malware Cleanup, `proactive` for Proactive Defensetypestringallowed]( proactiveaverequireduusers}(helptList of users to enable the feature for. If not specified, the feature will be enabled by default for all new users.typelistschema}typestringsnullableuuufeature-management disable}( return_type#FeaturesManagementEditAgentResponsehelpDDisable a feature for specified users or all new ones (set defaults)typedictcli}users]rootasschema}(feature}(helpKAllowed values: `av` for Malware Cleanup, `proactive` for Proactive Defensetypestringallowed]( proactiveaverequireduusers}(helpvList of users to disable the feature for. If not specified, the feature will be disabled by default for all new users.typelistschema}typestringsnullableuuuu.defence360agent/feature_management/rpc/schema/update.yaml0000644000000000000000000000231500000000000020570 0ustar feature-management enable: return_type: FeaturesManagementEditAgentResponse help: Enable a feature for specified users or all new ones (set defaults) type: dict cli: users: - root schema: feature: help: "Allowed values: `av` for Malware Cleanup, `proactive` for Proactive Defense" type: string allowed: - proactive - av required: true users: help: List of users to enable the feature for. If not specified, the feature will be enabled by default for all new users. type: list schema: type: string nullable: true feature-management disable: return_type: FeaturesManagementEditAgentResponse help: Disable a feature for specified users or all new ones (set defaults) type: dict cli: users: - root schema: feature: help: "Allowed values: `av` for Malware Cleanup, `proactive` for Proactive Defense" type: string allowed: - proactive - av required: true users: help: List of users to disable the feature for. If not specified, the feature will be disabled by default for all new users. type: list schema: type: string nullable: true defence360agent/feature_management/utils.py0000644000000000000000000001167400000000000016120 0ustar import logging import os from itertools import chain from typing import List, Any from defence360agent.contracts.config import Core from defence360agent.feature_management import hooks from defence360agent.feature_management.model import FeatureManagementPerms from defence360agent.model import instance from defence360agent.utils import ( execute_iterable_expression, is_safe_subdir_name, rmtree, ) from .lookup import features logger = logging.getLogger(__name__) async def set_feature(user: str, feature: str, value: str) -> bool: """Sets a `feature` to `value` for a given `user`. Calls appropriate hook and returns its (bool) result. Logs the result of setting change. If hook fails rollbacks changes to database. """ with instance.db.atomic() as trx: perm = FeatureManagementPerms.get_perm(user) perm.set_feature(feature, value) hook = hooks.get_hook(feature) ok = hook(user, value) if ok: logger.info( "Applied setting %s=%s for user %s", feature, value, user ) else: logger.error( "Failed to apply setting %s=%s for user %s", feature, value, user, ) trx.rollback() return ok async def update_users( feature: str, users: List[str], value: Any, existing_users: List[str] ): result = {"succeeded": [], "failed": []} for user in users: if user not in existing_users: logger.warning("No such user: %s", user) continue if await set_feature(user, feature, value): result["succeeded"].append(user) else: result["failed"].append(user) return result async def update_default(feature: str, value: Any): perm = FeatureManagementPerms.get_default() perm.set_feature(feature, value) hook = hooks.get_hook(feature) return hook(None, value) async def sync_users(users: List[str]) -> bool: """Synchronize existing permissions with panel users""" panel_users = set(users) perm_users = FeatureManagementPerms.select(FeatureManagementPerms.user) perm_users = set(chain(*perm_users.tuples())) perms_to_remove = perm_users - panel_users perms_to_remove.remove(FeatureManagementPerms.DEFAULT) if perms_to_remove: logger.info("Remove permissions of users %s", perms_to_remove) def expression(perms_to_remove): return FeatureManagementPerms.delete().where( FeatureManagementPerms.user.in_(perms_to_remove) ) execute_iterable_expression(expression, list(perms_to_remove)) for user in perms_to_remove: if not is_safe_subdir_name(user): continue target = os.path.join(Core.USER_CONFDIR, user) try: rmtree(target) except FileNotFoundError: pass except OSError as e: logger.warning( "Failed to remove user_config dir %s: %s", target, e ) perms_to_add = panel_users - perm_users if perms_to_add: logger.info("Add permissions to users %s", perms_to_add) for user in perms_to_add: perm = FeatureManagementPerms.get_perm(user) for feature in features: value = perm.get_feature(feature) callback = hooks.get_hook(feature) callback(user, value) return bool(perms_to_add) or bool(perms_to_remove) async def reset_features(**features): """Sets feature values for all existing users in feature management database to given values in `features`.""" users = list( chain( *FeatureManagementPerms.select(FeatureManagementPerms.user) .where( FeatureManagementPerms.user != FeatureManagementPerms.DEFAULT ) .tuples() ) ) def expression(chunk, feature, value): return FeatureManagementPerms.update(**{feature: value}).where( FeatureManagementPerms.user.in_(chunk) ) for feature, value in features.items(): hook = hooks.get_hook(feature) # Hooks touch the filesystem, so they run before the write # transaction opens: on a server with many users, doing this per user # inside the transaction holds the SQLite write lock for tens of # seconds and starves every other writer. applied = [] for user in users: if hook(user, value): applied.append(user) else: logger.error( "Failed to apply setting %s=%s for user %s", feature, value, user, ) execute_iterable_expression(expression, applied, feature, value) for user in applied: logger.info( "Applied setting %s=%s for user %s", feature, value, user ) defence360agent/files/0000755000000000000000000000000000000000000011650 5ustar defence360agent/files/__init__.py0000644000000000000000000014501600000000000013770 0ustar """Utilities for managing local file storage synchronised with a remote server. Files are divided into types: signatures, modsecurity bundles, ip white lists, etc. Each type is represented by an Index instance. Index has a local subdirectory and a description that contains its files' metadata used to decide if the update is necessary. """ import asyncio import datetime as DT import hashlib import http.client import io import json import math import os import pathlib import random import shutil import socket import time import zipfile import urllib.error import urllib.request from collections import defaultdict, namedtuple from contextlib import ExitStack, suppress, contextmanager from email.utils import formatdate, parsedate_to_datetime from gzip import GzipFile from itertools import chain from logging import getLogger from packaging.version import Version from typing import ( Any, BinaryIO, Dict, Iterable, List, Optional, Set, Tuple, Union, ) from urllib.parse import urlparse from defence360agent.contracts import config from defence360agent.contracts.license import LicenseCLN from defence360agent.subsys.panels.base import PanelException from defence360agent.utils import file_hash, retry_on, run_with_umask from defence360agent.utils.common import rate_limit, HOUR from defence360agent.utils.threads import to_thread from defence360agent.utils.net_transport import ( UrlTransport, RandomIpChooserWithIPv6Toggle, ) from defence360agent.utils.zipsafe import safe_extractall as _safe_extractall from .hooks import default_hook logger = getLogger(__name__) _IPV6_DISABLED_STATE = pathlib.Path("/var/imunify360/.ipv6_disabled") _SYSCTL_DISABLE_IPV6 = "/proc/sys/net/ipv6/conf/all/disable_ipv6" _MOD_PAR_PATH = "/sys/module/{mod}/parameters/{parameter}" def _is_kernel_ipv6_disabled() -> bool: """Check whether IPv6 is disabled at the kernel level. Reads the module parameter and the runtime sysctl without depending on the im360 package. """ param_file = _MOD_PAR_PATH.format(mod="ipv6", parameter="disable") try: with open(param_file) as f: if f.read().strip() != "0": return True except OSError: # ipv6 module is absent return True try: with open(_SYSCTL_DISABLE_IPV6) as f: if f.read().strip() == "1": return True except OSError: pass return False # static file types EULA = "eula" SIGS = "sigs" # malware signatures REALTIME_AV_CONF = "realtime-av-conf" WP_RULES = "wp-rules" GEO = "geo" FILES_DIR = pathlib.Path("/var/imunify360/files") BASE_URL = "https://files.imunify360.com/static/" # chunk size for network and file operations, in bytes _BUFSIZE = 32 * 1024 _MAX_TRIES_FOR_DOWNLOAD = 10 _TIMEOUT_MULTIPLICATOR = 0.025 """ >>> _MAX_TRIES_FOR_DOWNLOAD = 10 >>> _TIMEOUT_MULTIPLICATOR = 0.025 >>> [(1 << i) * _TIMEOUT_MULTIPLICATOR for i in range(1, _MAX_TRIES_FOR_DOWNLOAD)] # noqa [0.05, 0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 6.4, 12.8] """ #: sentinel: mtime for a missing/never modified file _NEVER = -math.inf # https://github.com/python/typing/issues/182 JSONType = Union[str, int, float, bool, None, Dict[str, Any], List[Any]] # ip chooser and urllib transport wrapper — lazy-initialized on first use # to avoid loading the SSL CA store (~1.5-2 MB) at import time (DEF-39725) _IP_CHOOSER: Optional[RandomIpChooserWithIPv6Toggle] = None _TRANSPORT: Optional[UrlTransport] = None def _should_disable_ipv6() -> bool: """Check if IPv6 should be disabled at startup. True when either the kernel has disabled IPv6 or a previous agent run persisted the disabled state after a runtime network failure. """ return _is_kernel_ipv6_disabled() or _IPV6_DISABLED_STATE.exists() def _persist_ipv6_disabled() -> None: """Persist IPv6 disabled state so it survives agent restarts.""" try: _IPV6_DISABLED_STATE.touch() except OSError: logger.debug("Could not persist IPv6 disabled state", exc_info=True) def _get_ip_chooser() -> RandomIpChooserWithIPv6Toggle: global _IP_CHOOSER if _IP_CHOOSER is None: ipv6_enabled = not _should_disable_ipv6() _IP_CHOOSER = RandomIpChooserWithIPv6Toggle(ipv6_enabled=ipv6_enabled) if not ipv6_enabled: logger.info( "IPv6 disabled at startup (kernel flag or persisted state)" ) return _IP_CHOOSER def _get_transport() -> UrlTransport: global _TRANSPORT if _TRANSPORT is None: _TRANSPORT = UrlTransport(ip_chooser=_get_ip_chooser()) return _TRANSPORT class IntegrityError(RuntimeError): """Raised when on disk content does not match hashes in description.json""" class UpdateError(RuntimeError): """Raised on other errors during files update. Possible reasons are: * server returns non 200 status; * hash mismatched between downloaded content and description.json; * urllib errors; * JSON decoding errors; * errors while writing to disk. """ async def _log_failed_update(exc, i): logger.warning( "Files update failed with error: {err}, try: {try_}".format( err=exc, try_=i ) ) # exponential backoff await asyncio.sleep(random.randrange(1 << i) * _TIMEOUT_MULTIPLICATOR) def _open_with_mode(path: os.PathLike, mode: int) -> BinaryIO: """Open file at `path` using permission `mode` for writing in binary mode and return file object.""" with run_with_umask(0): fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode) return os.fdopen(fd, "wb") def _fetch_json_sync(url, timeout) -> JSONType: with _fetch_url(url, timeout=timeout) as response: return json.load( io.TextIOWrapper( response["file"], encoding=response["headers"].get_content_charset("utf-8"), ) ) def _disable_ipv6_on_network_error(url: str, exc: Exception) -> None: chooser = _get_ip_chooser() if chooser.is_ipv6_enabled() and chooser.last_ip_was_ipv6(): logger.warning( "Network error for %s via IPv6 IP %r, disabling IPv6. Error: %r", url, chooser.last_ip(), exc, ) chooser.disable_ipv6() _persist_ipv6_disabled() @retry_on( UpdateError, on_error=_log_failed_update, max_tries=_MAX_TRIES_FOR_DOWNLOAD ) async def _fetch_json(url: str, timeout) -> JSONType: """Download and decode JSON from *url*. Return decoded JSON. Raise UpdateError: * HTTP response status code is not 200; * Unicode or JSON decoding fails; * on time outs during HTTP request; * on other HTTP errors. """ loop = asyncio.get_event_loop() try: return await loop.run_in_executor(None, _fetch_json_sync, url, timeout) except (UnicodeDecodeError, json.JSONDecodeError) as e: raise UpdateError("json decode error [{}] for url {}".format(e, url)) except socket.timeout as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} timed out".format(url)) except ConnectionResetError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} reset".format(url)) except EOFError as e: raise UpdateError( f"eof error while updating files, url: {url}, err: {e}" ) except (http.client.HTTPException, urllib.error.URLError) as e: _disable_ipv6_on_network_error(url, e) raise UpdateError( "urllib/http error while updating files, url: {}, err: {}".format( url, e ) ) except OSError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError(f"Can't fetch {url}, reason: {e}") def _perform_http_head_sync( # NOSONAR pylint:W0102 url: str, timeout: float, *, headers={} ): """Perform HEAD http request to *url* with *timeout* & *headers*.""" req = urllib.request.Request( url, headers={ "Imunify-Server-Id": LicenseCLN.get_server_id() or "", **headers, }, method="HEAD", ) with _get_transport().open(req, timeout=timeout) as r: return r.code, r.headers @retry_on( UpdateError, on_error=_log_failed_update, max_tries=_MAX_TRIES_FOR_DOWNLOAD ) async def _need_to_download( url: str, current_mtime: float, timeout: float ) -> bool: """Check if we need to download description.json file: - perform HEAD request if local file exists and older return True otherwise return False """ if current_mtime is _NEVER: # file has never been updated return True # need to download it formatted_mtime = formatdate(current_mtime, usegmt=True) try: code, headers = await to_thread( _perform_http_head_sync, url, timeout, headers={"If-Modified-Since": formatted_mtime}, ) except socket.timeout as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} timed out".format(url)) except ConnectionResetError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} reset".format(url)) except (http.client.HTTPException, urllib.error.URLError) as e: if hasattr(e, "code") and e.code == 304: return False _disable_ipv6_on_network_error(url, e) raise UpdateError( "urllib/http error while updating files, url: {}, err: {}".format( url, e ) ) else: if code != 200: raise UpdateError( f"Unexpected http code {code!r} for {url}" ) # pragma: no cover with suppress(Exception): last_mtime = parsedate_to_datetime( headers["Last-Modified"] ).timestamp() if last_mtime <= current_mtime: # file on the server NOT newer logger.warning( "Got code %r, but last modification date %s is earlier" " than or equal to the date provided in the" " If-Modified-Since header, the origin server SHOULD" " generate a 304 (Not Modified) response [rfc7232]." " Here's curl cmd:\ncurl -s -I -w '%%{http_code}' -H" " 'If-Modified-Since: %s' '%s'", code, headers["Last-Modified"], formatted_mtime, url, ) return True # file has been modified since current mtime, re-download @contextmanager def _fetch_url(url: str, *, timeout: float, compress=True): """ Fetch *url* as binary file. If *compress* is true, ungzipping is done automatically if necessary. """ parameters = {} if timeout is not None: # use default timeout instead None parameters["timeout"] = timeout req_headers = {"Imunify-Server-Id": LicenseCLN.get_server_id() or ""} if compress: # express preference for gzip but don't forbid identity encoding req_headers.update({"Accept-Encoding": "gzip"}) req = urllib.request.Request(url, headers=req_headers) with _get_transport().open( req, **parameters ) as response, ExitStack() as stack: # check whether response is gzipped regardless *compress* arg gzipped = response.headers.get("Content-Encoding") == "gzip" if ( compress and not gzipped and response.headers.get("Content-Type") != "application/zip" ): logger.info( "Requested gzip but got Content-Encoding=%r." " Read response as is [identity]. Headers: %s," " as curl cmd:\ncurl -Is -H 'Accept-Encoding: gzip' '%s'", response.headers.get("Content-Encoding"), response.headers.items(), url, ) yield { "file": ( stack.enter_context(GzipFile(fileobj=response)) if gzipped else response ), "headers": response.headers, } def _fetch_n_md5sum_url( url, dest_file: BinaryIO, timeout, *, compress, md5sum ): """ Fetch *url* to *dest_file* and return its md5sum. Raise *urllib.error.ContentTooShortError* if the downloaded file has unexpected length. """ md5 = hashlib.md5() initial_file_offset = dest_file.tell() with _fetch_url(url, timeout=timeout, compress=compress) as response: while chunk := response["file"].read(_BUFSIZE): # NOSONAR md5.update(chunk) dest_file.write(chunk) if not response["headers"].get("Content-Encoding") == "gzip": # Content-Length is compressed size # -> no point in comparing with the uncompressed result file_length = dest_file.tell() - initial_file_offset # make sure the file has been downloaded correctly # Content-Length may not be set if exist header # Transfer-Encoding: chunked content_length_header = response["headers"].get("Content-Length", None) if content_length_header is not None: expected_file_length = int(content_length_header) if expected_file_length != file_length: raise urllib.error.ContentTooShortError( message="{got} bytes read, {diff} more expected".format( got=file_length, diff=expected_file_length - file_length, ), content=None, ) got_md5sum = md5.hexdigest() if md5sum is not None and got_md5sum != md5sum: raise UpdateError( f"content fetched from {url} does not match hash:" f" expected={md5sum}, got={got_md5sum}" ) return got_md5sum async def _fetch_and_save_should_retry_handler(exc: Exception, i: int) -> bool: return "HTTP Error 404" not in str(exc) @retry_on( UpdateError, on_error=_log_failed_update, max_tries=_MAX_TRIES_FOR_DOWNLOAD, should_retry=_fetch_and_save_should_retry_handler, ) async def _fetch_and_save( url: str, dest_path: os.PathLike, timeout, *, dest_mode: int, compress=True, md5sum=None, ) -> str: """Fetch bytes from `url`, save them to `dest_path`, and return md5 checksum of downloaded content. Raise UpdateError: * HTTP response status code is not 200; * on time outs during HTTP request; * on other HTTP errors. """ try: with _open_with_mode(dest_path, dest_mode) as dest_file: return await to_thread( _fetch_n_md5sum_url, url, dest_file, timeout, compress=compress, md5sum=md5sum, ) except socket.timeout as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} timed out".format(url)) except ConnectionResetError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError("request to {} reset".format(url)) except EOFError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError( f"eof error while updating files, url: {url}, err: {e}" ) except (http.client.HTTPException, urllib.error.URLError) as e: _disable_ipv6_on_network_error(url, e) raise UpdateError( "urllib/http error while updating files, url: {}, err: {}".format( url, e ) ) except OSError as e: _disable_ipv6_on_network_error(url, e) raise UpdateError(f"Can't fetch {url} to {dest_path}, reason: {e}") _Item = namedtuple("_Item", ["url", "md5sum"]) def _items(data: Any) -> Set[_Item]: """Return a set of _Item for easy manipulation.""" return {_Item(item["url"], item["md5sum"]) for item in data["items"]} def check_mode_dirs(dirname, dir_perm, file_perm): """Check and change file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm """ def _os_chmod(file_dir_path, permission): try: current_mode = os.lstat(file_dir_path).st_mode & 0o777 if current_mode != permission and not os.path.islink( file_dir_path ): logger.warning( "Fixing wrong permission to file/dir" " %s [%s] expected [%s] (not symlink)", file_dir_path, oct(current_mode), oct(permission), ) os.chmod(file_dir_path, permission) except PermissionError: logger.error( "Failed to change permission to file %s", file_dir_path ) _os_chmod(dirname, dir_perm) for path, dirs, files in os.walk(dirname): for directory in dirs: _os_chmod(os.path.join(path, directory), dir_perm) for name in files: _os_chmod(os.path.join(path, name), file_perm) def _fix_directory_structure( description_path: pathlib.Path, files_path: pathlib.Path = FILES_DIR ) -> None: """ Try to fix the structure of /var/imunify360/files/ when NotADirectoryError happens. It indicates that some part in the path is a file: /var/imunify360/files/sigs <- is a file => open("/var/imunify360/files/sigs/v1/description.json") will fail. We try to rectify it by deleting the file but up to FILES_DIR. """ assert files_path in description_path.parents _dir = description_path.parent topmost_dir = _dir while _dir != files_path: if _dir.is_file(): _dir.unlink(missing_ok=True) topmost_dir.mkdir(parents=True, exist_ok=True) break _dir = _dir.parent class Index: # one lock is shared via Index and that allows # more than one instance of Index to co-exist _lock = defaultdict(asyncio.Lock) # type: Dict[Any, asyncio.Lock] _HOOKS = defaultdict(set) # type: Dict[str, Set[Any]] _PATHS = {} # type: Dict[str, str] _PERMS = {} # type: Dict[str, Dict[str, int]] _TYPES = set() # type: Set[str] _ESSENTIAL_TYPES = set() # type: Set[str] _ALL_ZIP_SUPPORT = {} # type: Dict[str, bool] _URL_PATH_PREFIX = "/static" _throttled_log_error = rate_limit(period=4 * HOUR)(logger.error) def __init__(self, type_, integrity_check=True): """ :param bool integrity_check: check if last update did not break anything (by interrupting it in the middle or another programmatic error) :raise IntegrityError: """ if type_ not in self._TYPES: raise ValueError( f"Trying to initiate unregistered file type {type_}. Allowed" f" types {self._TYPES}" ) self.type = type_ self._is_blank = False self._json = {"items": []} path = self._descriptionfile_path() try: with open(path) as f: self._json = json.load(f) except NotADirectoryError: Index._throttled_log_error("Path %s has a file in parents", path) _fix_directory_structure(pathlib.Path(path), FILES_DIR) except ( FileNotFoundError, UnicodeDecodeError, json.JSONDecodeError, ) as e: if integrity_check: raise IntegrityError( "cannot read description file {}".format(path) ) from e self._is_blank = True if integrity_check: bad_files = self._corrupted_files() if len(bad_files): raise IntegrityError( "some files are missing or corrupted: {}".format( ", ".join(bad_files) ) ) if not self._is_blank: self.check_mode_dirs() def __eq__(self, other): return ( self.__class__ == other.__class__ and self.type == other.type and self._is_blank == other._is_blank and self._json == other._json ) def __repr__(self): # pragma: no cover return ( f"<{self.__class__.__name__}(type_={self.type})" f" is_blank={self._is_blank}, " f"json={{<{len(self.items())}" " item(s)>}>" ) def validate(self, files_path: os.PathLike) -> None: """Whether *files_path* dir may be used for this type's file group. :raises: IntegrityError """ logger.info("Validating [%s]: %s", self.type, files_path) FileGroup = self._make_file_group( files_path ) # noqa NOSONAR disable python:S117 FileGroup(self.type, integrity_check=True) def _make_file_group(self, files_path: os.PathLike): """ Return FileGroup class: Index class with local path == *files_path*. """ class FileGroup(self.__class__): @classmethod def files_path(cls, type_: str) -> str: """Return local base path for given file type.""" assert type_ == self.type return os.fspath(files_path) return FileGroup def check_mode_dirs(self): perms = Index._PERMS[self.type] check_mode_dirs( os.path.normpath( os.path.join(FILES_DIR, Index._PATHS[self.type], os.pardir) ), perms["dir"], perms["file"], ) @classmethod def add_type( cls, type_: str, relative_path: str, dir_perm: int, file_perm: int, *, all_zip: bool = False, essential: bool = True, ) -> None: """Add a type to known file types. * relative_path is a relative path to all files for that type. * dir_perm is permission mask used to create directories. * file_perm is permission mask used to create files. * all_zip is a flag which shows whether that type of files can be downloaded in all.zip archive. all.zip is expected to be on the server. * essential is whether the agent can start if there are errors updating that type. """ cls._TYPES.add(type_) if essential: cls._ESSENTIAL_TYPES.add(type_) cls._PATHS[type_] = relative_path cls._PERMS[type_] = {"dir": dir_perm, "file": file_perm} cls._ALL_ZIP_SUPPORT[type_] = all_zip @classmethod async def essential_files_exist(cls) -> bool: """Whether essential files exist. Note: the files may be corrupted (integrity check is not performed). """ # use the existence of the description files as a proxy return all( not Index(type_, integrity_check=False)._is_blank for type_ in cls._ESSENTIAL_TYPES ) @classmethod def types(cls) -> Set[str]: """Return a set of all known files types.""" return cls._TYPES.copy() @classmethod def files_path(cls, type_: str) -> str: """Return local base path for given file type.""" return os.path.join(FILES_DIR, cls._PATHS[type_]) def _descriptionfile_path(self, latest=False) -> str: """Return local path for description.json for current index.""" if latest and self.type in config.FilesUpdate.DISABLED: # for disabled types, use the latest veriosn path return os.path.join( self._descriptionfile_path_latest(), "description.json" ) return os.path.join(self.files_path(self.type), "description.json") def _descriptionfile_path_latest(self) -> str: lts = [x["dir"] for x in self._get_list().values() if x["latest"]][0] return lts def _corrupted_files(self) -> Set[str]: """Return a set of file paths that are missing or corrupted.""" bad_files = set() for item in _items(self._json): path = self.localfilepath(item.url) try: actual = file_hash(path, hashlib.md5, _BUFSIZE) except FileNotFoundError: bad_files.add(path) continue if actual != item.md5sum: bad_files.add(path) return bad_files @classmethod def locked(cls, type_): """ usage example: >> async with Index.locked(WHITELISTS): ... """ return cls._lock[type_] def files(self) -> Iterable[str]: """Return iterable over all files in index.""" return (self.localfilepath(item.url) for item in _items(self._json)) def items(self): """Return 'items' field from JSON description.""" return self._json["items"] def _descriptionfile_mtime(self, default=_NEVER) -> float: """Return mtime of description file if it exists, otherwise -math.inf""" try: return os.stat(self._descriptionfile_path(latest=True)).st_mtime except OSError: return default def _is_outdated(self) -> bool: """Return True if last update was too late in the past.""" _desc_mtime = self._descriptionfile_mtime() if not _desc_mtime: return True # pragma: no cover return _desc_mtime + config.FilesUpdate.PERIOD < time.time() async def is_update_needed(self, timeout: float) -> bool: """Return True if update from server is needed for current index.""" return ( self._is_blank or len(self._corrupted_files()) > 0 or ( self._is_outdated() and await _need_to_download( self._descriptionfile_url(self.type), self._descriptionfile_mtime(), timeout, ) ) ) def _makedirs(self, dirname, dir_mode, exist_ok=False): """Create local directory for current index.""" try: with run_with_umask(0): os.makedirs(dirname, mode=dir_mode, exist_ok=exist_ok) except OSError as e: raise UpdateError(str(e)) from e async def _update_files( self, files_path: pathlib.Path, to_update: Set[_Item], timeout ) -> None: """ Fetch files from *to_update* set, verify hashes, save to *files_path*. """ FileGroup = self._make_file_group( files_path ) # noqa NOSONAR disable python:S117 fg = FileGroup(self.type, integrity_check=False) dir_mode = fg._PERMS[fg.type]["dir"] # NOSONAR disable python:W0212 file_mode = fg._PERMS[fg.type]["file"] # NOSONAR disable python:W0212 for item in to_update: filename = fg.localfilepath(item.url) dirname = os.path.dirname(filename) if not os.path.isdir(dirname): self._makedirs(dirname, dir_mode, exist_ok=False) await _fetch_and_save( item.url, filename, timeout, dest_mode=file_mode, md5sum=item.md5sum, ) def _calculate_changes( self, remote_items: Set[_Item] ) -> Tuple[Set[_Item], Set[str]]: """Figure out what should be updated based on current items, file system state and remote items. Return tuple of files to fetch and files to delete. Files to fetch is a set of _Item. Files to delete is a set of file paths.""" local_items = _items(self._json) local_files = {self.localfilepath(item.url) for item in local_items} remote_files = {self.localfilepath(item.url) for item in remote_items} to_remove = local_files - remote_files bad_files = self._corrupted_files() local_set = { item for item in local_items if self.localfilepath(item.url) not in bad_files } to_update = remote_items - local_set return to_update, to_remove @classmethod def _descriptionfile_url(cls, type_: str) -> str: """Return remote path for description.json""" return "{}{}/description.json".format(BASE_URL, cls._PATHS[type_]) @classmethod def _all_zip_url(cls, type_: str) -> str: """Return remote path for all.zip""" return "{}{}/all.zip".format(BASE_URL, cls._PATHS[type_]) @staticmethod def _all_zip_cleanup(files_path, all_zip_localpath, remove_files=False): try: os.unlink(all_zip_localpath) except OSError as e: logger.warning( "failed to remove %s: %s", all_zip_localpath, str(e) ) if remove_files: logger.info("Removing old path on all.zip update: %s", files_path) shutil.rmtree(files_path, ignore_errors=True) @staticmethod def _generate_new_path(live_path: pathlib.Path) -> pathlib.Path: """Generate new base local path for *live_path* files. It should be on the same filesystem partition as *live_path* so that the rename would be atomic. """ new_suffix = DT.datetime.utcnow().strftime("_%Y-%m-%dT%H%M%S.%fZ") return live_path.with_name(live_path.name + new_suffix) async def _run_update_all_zip(self, timeout) -> bool: """ Update current type of files using all.zip archive. Directory with current type of files will be cleared and replaced with all.zip contents. all.zip is expected to be on the server Return whether updated. :param timeout: :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) """ live_path = pathlib.Path(self.files_path(self.type)) new_path = Index._generate_new_path(live_path) archive_path = new_path.with_name(new_path.name + "all.zip") file_mode = self._PERMS[self.type]["file"] dir_mode = self._PERMS[self.type]["dir"] all_zip_url = self._all_zip_url(self.type) with ExitStack() as rollback_stack: # make new download dir self._makedirs(new_path, dir_mode, exist_ok=False) rollback_stack.callback( Index._all_zip_cleanup, new_path, archive_path, remove_files=True, ) # download the archive # TODO: DEF-16354 check md5sum for all.zip _ = await _fetch_and_save( all_zip_url, archive_path, timeout, dest_mode=file_mode, compress=False, ) # extract files to new dir with right permissions & verify try: with zipfile.ZipFile(archive_path, "r") as archive: # NOTE: this also verifies crc-32 checksum for files _safe_extractall(archive, new_path) # set mode for root, directories, filenames in os.walk(new_path): for directory in directories: os.chmod(os.path.join(root, directory), dir_mode) for filename in filenames: os.chmod(os.path.join(root, filename), file_mode) # verify against included description.json self.validate(new_path) # create symlink to new dir, replace *live* with the symlink old_path = self._replace_live_with_new_dir(new_path, live_path) except ( EOFError, IntegrityError, OSError, ValueError, zipfile.BadZipfile, zipfile.LargeZipFile, ) as e: raise UpdateError(str(e)) from e # no exception, clear the rollback stack rollback_stack.pop_all() # cleanup: remove old dir & new all.zip Index._all_zip_cleanup( old_path, archive_path, remove_files=bool(old_path) ) # DEF-41801: when the type is in FILES_UPDATE.disabled_types, # _replace_live_with_new_dir skipped the symlink flip, so no # new files are actually live — report not-updated so downstream # hooks (e.g. update_vendors → Apache reload) stay quiet. return self.type not in config.FilesUpdate.DISABLED async def update_to(self, version: str, force: bool = False) -> None: """Update to the version specified in *version*. :param version: version to update to :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) """ # change symlink to exact version live_path = pathlib.Path(self.files_path(self.type)) if not live_path.is_symlink(): raise UpdateError( "Cannot update %s, because it is not a symlink: %s" % (self.type, live_path) ) if version == "latest": versions = self._get_list() version = [ ver for ver, prop in versions.items() if prop["latest"] ][0] logger.info( "Try to update to latest version %s %s", self.type, version ) current_path = live_path.resolve(strict=False) try: if ( Version((current_path / "VERSION").read_text().strip()) == Version(version) and not force ): raise UpdateError( f"Version {version} is already set for {self.type}" ) except FileNotFoundError: raise UpdateError( "Cannot update %s, because current version doesn't have" " VERSION file: %s" % (self.type, current_path) ) # check if version exists for path in live_path.parent.iterdir(): if not path.is_symlink() and path.is_dir(): if Version((path / "VERSION").read_text().strip()) == Version( version ): new_live_path = path.with_name(path.name + "live") new_live_path.symlink_to(path, target_is_directory=True) new_live_path.rename(live_path) await self._run_hooks(is_updated=True) return raise UpdateError("Version %s not found in %s" % (version, self.type)) def _get_list(self) -> Dict[Version, Dict[str, bool | str]]: live_path = pathlib.Path(self.files_path(self.type)) if not live_path.is_symlink(): return {} current_path = live_path.resolve(strict=False) result = {} max_version = Version("0") for path in live_path.parent.iterdir(): if path.is_symlink(): # skip live path symlink continue # if /var is symlink, we need to resolve it too to compare if path.resolve() == current_path: current = True else: current = False if (version_file := path / "VERSION").exists(): version = None try: version = version_file.read_text() _ver = Version(version) result[_ver] = { "current": current, "latest": False, "dir": str(path), } if _ver > max_version: max_version = _ver except ValueError: logger.error( "Version file %s is not valid: %s", version_file, version, ) continue if max_version > Version("0"): result[max_version]["latest"] = True return result def get_list(self) -> List[str]: """Return list of versions available in the index.""" result = [] for version, prop in sorted( self._get_list().items(), key=lambda x: x[0] ): marker = ( " (current)" if prop["current"] else " (latest)" if prop["latest"] else "" ) result.append(f"{version}{marker}") return result def _clean_old_versions(self) -> None: """Remove old versions of files. This is done by removing old directories in the path, that older than 30 days. """ # remove old versions of files live_path = pathlib.Path(self.files_path(self.type)) if not live_path.is_symlink(): return current_path = live_path.resolve(strict=False) for path in live_path.parent.iterdir(): days_old = ( DT.datetime.now(DT.timezone.utc) - DT.datetime.fromtimestamp( path.stat().st_mtime, DT.timezone.utc ) ).days if ( path.is_dir() and not path.is_symlink() and path != current_path and (days_old > config.FilesUpdate.DAYS_TO_KEEP) ): logger.info("Removing old version of %s: %s", self.type, path) shutil.rmtree(path, ignore_errors=True) def _replace_live_with_new_dir( self, new_path: pathlib.Path, live_path: pathlib.Path ) -> Optional[pathlib.Path]: """Replace *live_path* with *new_path*. Return *old_path* :raises: OSError """ if self.type in config.FilesUpdate.DISABLED: self._clean_old_versions() logger.info( "Skipping update for %s, because it is disabled. New files" " stored in %s", self.type, new_path, ) return None new_live_path = new_path.with_name(new_path.name + "live") moved_path = None with ExitStack() as rollback_stack: new_live_path.symlink_to(new_path, target_is_directory=True) rollback_stack.callback(new_live_path.unlink) # save the path to old dir for the cleanup old_path = ( live_path.resolve(strict=False) if live_path.is_symlink() else None ) # switch to the new version # NOTE: nothing until this point touched old version; # the rename should be atomic # (paths are on the same partition) for last in range(2): # pragma: no branch try: new_live_path.rename(live_path) break except IsADirectoryError: if last: # give up (keep old) raise # pragma: no cover # live_path is a directory # (old agent version or tests) # move it so that the rename above could happen if not live_path.is_symlink(): # pragma: no branch # use unique to the current update name moved_path = new_live_path.with_name( new_live_path.name + ".live-moved" ) logger.info( "Moving %s [live] to %s," " to rename %s to it [live]", live_path, moved_path, new_live_path, ) live_path.replace(moved_path) # if enabling new_live fails the 2nd time, # try to move back, to restore old dir rollback_stack.callback(moved_path.replace, live_path) if moved_path is not None: shutil.rmtree(moved_path, ignore_errors=True) # no exception, clear the rollback stack rollback_stack.pop_all() return old_path async def _run_update(self, timeout) -> bool: """ Run update, return whether updated. :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) """ url = self._descriptionfile_url(self.type) as_json = await _fetch_json(url, timeout=timeout) to_update, to_remove = self._calculate_changes(_items(as_json)) need_update = to_update or to_remove if not need_update: logger.info("updating %s: nothing to update.", self.type) self._touch() # postpone the next try for FilesUpdate.PERIOD return False # not updated # perform atomic update live_path = pathlib.Path(self.files_path(self.type)) # note: it is ok if the symlink changes before .resolve() is called old_path = ( live_path.resolve(strict=False) if live_path.is_symlink() else None ) new_path = Index._generate_new_path(live_path) # make new download dir with ExitStack() as rollback_stack: self._makedirs( new_path, self._PERMS[self.type]["dir"], exist_ok=False ) rollback_stack.callback( shutil.rmtree, new_path, ignore_errors=True ) # copy all files from *old* dir to *new* dir except those # that needs updating from_path = ( old_path if old_path and old_path.is_dir() else live_path ) if from_path.is_dir(): await Index._copytree( from_path, new_path, to_remove.union( self.localfilepath(item.url) for item in to_update ), ) # download *to_update* files to *new_path* await self._update_files(new_path, to_update, timeout=timeout) try: # write description.json with _open_with_mode( new_path / "description.json", self._PERMS[self.type]["file"], ) as file: file.write(json.dumps(as_json).encode()) # verify against included description.json self.validate(new_path) # create symlink to new dir, replace *live* with the symlink old_path = self._replace_live_with_new_dir(new_path, live_path) except (IntegrityError, OSError) as e: raise UpdateError(str(e)) from e # no exception, clear the rollback stack rollback_stack.pop_all() # cleanup: remove old path on success if old_path and old_path.is_dir(): logger.info( "Removing old path on file by file update: %s", old_path ) shutil.rmtree(old_path, ignore_errors=True) # DEF-41801: see _run_update_all_zip — same rationale. return self.type not in config.FilesUpdate.DISABLED @staticmethod async def _copytree( from_dir: os.PathLike, to_dir: os.PathLike, ignored_paths: Set[str] ) -> None: """Copy *from_dir* to *to_dir* except for *ignored_paths*.""" def ignore_names(path, names): """Return names that should not be copied.""" assert isinstance(os.fspath(path), str) # no bytes here return frozenset( name for name in names if os.path.join(path, name) in ignored_paths ) await to_thread( shutil.copytree, from_dir, to_dir, symlinks=True, ignore=ignore_names, dirs_exist_ok=True, ) def localfilepath(self, url: str) -> str: """Return a local file path corresponding to URL.""" url_relpath = os.path.relpath( urlparse(url).path, self._URL_PATH_PREFIX ) type_path = self._PATHS[self.type] assert ( pathlib.Path(type_path) in pathlib.Path(url_relpath).parents ), "url ({}) does not fit file path ({})".format(url, type_path) relative_path = os.path.relpath(url_relpath, type_path) return os.path.join(self.files_path(self.type), relative_path) def _touch(self) -> None: """Update mtime of description.json file so it is fresh.""" try: path = self._descriptionfile_path(latest=True) if os.path.isfile(path): # pragma: no branch os.utime(path) except OSError as e: # pragma: no cover logger.warning(str(e)) async def _run_hooks(self, is_updated) -> None: for hook in chain(self._HOOKS[self.type], [default_hook]): try: await hook(self, is_updated) except (IntegrityError, PanelException) as e: logger.error("hook %s error: %s", hook, e) except Exception as e: logger.exception("hook %s error: %s", hook, e) logger.info( "%s files update finished%s", self.type, " (not updated)" * (not is_updated), ) async def update(self, force=False) -> None: """Run update for the current `type` of files. Normally update is performed when either is true: * index is never been fetched (description.json missing or broken); * last update was performed longer than configured period of time ago; * some local files are missing or have wrong content (md5 hash differs from description.json). If force is True then update is performed unconditionally. Raises asyncio.TimeoutError, UpdateError. """ timeout = config.FilesUpdate.TIMEOUT # total timeout if not force and not await self.is_update_needed(timeout): logger.info( "%s was updated less than %s minutes ago.", self.type, int(config.FilesUpdate.PERIOD // 60), ) await self._run_hooks(is_updated=False) return all_zip = self._is_blank and self._ALL_ZIP_SUPPORT[self.type] file_by_file = not all_zip if all_zip: log_str = "all.zip" logger.info("Updating %s files via %s", self.type, log_str) # Download updates using all.zip in case of empty or # corrupted description.json. # Initially we try to download updates using all.zip, if # error happened - download file by file. try: updated = await asyncio.wait_for( self._run_update_all_zip( config.FilesUpdate.SOCKET_TIMEOUT ), timeout, ) if updated: logger.info("Updated %s using %s", self.type, log_str) except (asyncio.TimeoutError, UpdateError) as e: logger.warning( "%s update error via %s: %s", self.type, log_str, e ) file_by_file = True if file_by_file: log_str = "file by file download" logger.info("Updating %s files via %s", self.type, log_str) try: updated = await asyncio.wait_for( self._run_update(config.FilesUpdate.SOCKET_TIMEOUT), timeout, ) if updated: logger.info("Updated %s using %s", self.type, log_str) except (asyncio.TimeoutError, UpdateError) as e: logger.warning( "%s update error via %s: %s", self.type, log_str, e ) await self._run_hooks(is_updated=False) # Ignore errors only for non-essential files if self.type in self._ESSENTIAL_TYPES: raise e else: return await self._run_hooks(is_updated=updated or force) @classmethod async def update_all( cls, only_type: Optional[str] = None, force=False, only_essential=False ) -> None: """Run update for all registered `types` of files. Raises asyncio.TimeoutError, UpdateError. """ if only_type: index = cls(only_type, integrity_check=False) async with cls.locked(only_type): await index.update(force) elif only_essential: logger.info("Updating essential files") for type_ in cls._ESSENTIAL_TYPES: index = cls(type_, integrity_check=False) async with cls.locked(type_): await index.update(force) else: logger.info("Updating all files") for type_ in cls._TYPES: index = cls(type_, integrity_check=False) async with cls.locked(type_): await index.update(force) @classmethod def add_hook(cls, type_: str, hook) -> None: """Add a hook for type_ to be called after successful update.""" cls._HOOKS[type_].add(hook) def configure() -> None: """Register required file types.""" Index.add_type(EULA, "eula/v1", 0o770, 0o660, all_zip=False) Index.add_type(SIGS, "sigs/v1", 0o775, 0o644, all_zip=True) Index.add_type( REALTIME_AV_CONF, "realtime-av-conf/v1", 0o770, 0o660, all_zip=False, ) Index.add_type( WP_RULES, "wp-rules/v1", 0o770, 0o660, all_zip=True, essential=False, ) Index.add_type(GEO, "geo/v1", 0o770, 0o660, all_zip=True, essential=False) update = Index.update_all essential_files_exist = Index.essential_files_exist async def update_and_log_error( only_type: Optional[str] = None, force=False ) -> None: """Run files.update and log Update/TimeoutErrors.""" try: return await Index.update_all(only_type, force) except (asyncio.TimeoutError, UpdateError) as err: logger.warning( "Failed to update files [%s] with error: %s", only_type, err ) async def update_all_no_fail_if_files_exist(): """Update all files. Don't fail if essential files exist.""" try: return await Index.update_all(only_essential=True) except (asyncio.TimeoutError, UpdateError) as err: if await Index.essential_files_exist(): logger.error( "Failed to update files [essential files exist]: %s", err ) else: # re-raise if isinstance(err, asyncio.TimeoutError): raise UpdateError from err # wrap else: raise defence360agent/files/__pycache__/0000755000000000000000000000000000000000000014060 5ustar defence360agent/files/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000022225400000000000021267 0ustar r_j PUdZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl Z ddlZddlZddlZddlZddlZddlmZmZddlmZmZmZddlmZmZddlmZddl m!Z!ddl"m#Z#dd l$m%Z%dd l&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/dd l0m1Z1dd l2m3Z3dd l4m5Z5ddl6m7Z7ddl8m9Z9m:Z:m;Z;ddlZ>ddl?m@Z@ddlAmBZBmCZCddlDmEZFddlGmHZHe#eIZJe jKdZLdZMdZNdeOfdZPdZQdZRdZSdZTdZUe jKd ZVd!ZWd"ZXd#ZYd$ZZ e j[ Z\e/e]e^e_eOde)e]e'fe+e'fZ`daae,eCebd%<dace,eBebd&<deOfd'ZddYd(ZedeCfd)ZfdeBfd*ZgGd+d,ehZiGd-d.ehZjd/Zkd0e jld1e^de(fd2Zmde`fd3Znd4e]d5eoddfd6Zpe:ejekeY7d4e]de`fd8Zqid9d4e]d:e_fd;Zre:ejekeY7d4e]dd?d4e]d:e_fd@ZtdAe(fdBZud5eodCe^deOfdDZve:ejekeYevEd>ddFd4e]dGe jldHe^de]fdIZwedJd4dKgZxdLe'de-exfdMZydNZzeVfdOe jKdPe jKddfdQZ{GdRdSZ|dYdTZ}e|j~Ze|jZ dZdVe,e]ddfdWZdXZdS)[aPUtilities for managing local file storage synchronised with a remote server. Files are divided into types: signatures, modsecurity bundles, ip white lists, etc. Each type is represented by an Index instance. Index has a local subdirectory and a description that contains its files' metadata used to decide if the update is necessary. N) defaultdict namedtuple) ExitStacksuppresscontextmanager) formatdateparsedate_to_datetime)GzipFile)chain) getLogger)Version) AnyBinaryIODictIterableListOptionalSetTupleUnion)urlparse)config) LicenseCLN)PanelException) file_hashretry_onrun_with_umask) rate_limitHOUR) to_thread) UrlTransportRandomIpChooserWithIPv6Toggle)safe_extractall) default_hookz/var/imunify360/.ipv6_disabledz(/proc/sys/net/ipv6/conf/all/disable_ipv6z(/sys/module/{mod}/parameters/{parameter}returnctdd} t|5}|dkr ddddS dddn #1swxYwYn#t $rYdSwxYw tt 5}|dkr ddddS dddn #1swxYwYn#t $rYnwxYwdS) zCheck whether IPv6 is disabled at the kernel level. Reads the module parameter and the runtime sysctl without depending on the im360 package. ipv6disable)mod parameter0NT1F) _MOD_PAR_PATHformatopenreadstripOSError_SYSCTL_DISABLE_IPV6) param_filefs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/__init__.py_is_kernel_ipv6_disabledr8Ds %%&I%FFJ *   vvxx~~3&&        &                tt & ' ' 1vvxx~~3&&        &                      5svA?,A3 A?' A?3A77A?:A7;A?? B  B C7%,C+ C7 C7+C//C72C/3C77 DDeulasigszrealtime-av-confzwp-rulesgeoz/var/imunify360/filesz$https://files.imunify360.com/static/i g? _IP_CHOOSER _TRANSPORTcPtptS)zCheck if IPv6 should be disabled at startup. True when either the kernel has disabled IPv6 or a previous agent run persisted the disabled state after a runtime network failure. )r8_IPV6_DISABLED_STATEexistsr7_should_disable_ipv6rD~s" $ % % F)=)D)D)F)FFrCc tdS#t$r tddYdSwxYw)z:Persist IPv6 disabled state so it survives agent restarts.z%Could not persist IPv6 disabled stateT)exc_infoN)r@touchr3loggerdebugrBrCr7_persist_ipv6_disabledrJs_M""$$$$$ MMM r!rOrBrCr7_get_transportrRs$!_->->??? rCceZdZdZdS)IntegrityErrorzERaised when on disk content does not match hashes in description.jsonN__name__ __module__ __qualname____doc__rBrCr7rTrTsOOOOrCrTceZdZdZdS) UpdateErrora Raised on other errors during files update. Possible reasons are: * server returns non 200 status; * hash mismatched between downloaded content and description.json; * urllib errors; * JSON decoding errors; * errors while writing to disk. NrUrBrCr7r[r[s    rCr[cKtd||tjt jd|ztzd{VdS)Nz2Files update failed with error: {err}, try: {try_})errtry_r$)rHwarningr/asynciosleeprandom randrange_TIMEOUT_MULTIPLICATORexcis r7_log_failed_updaterhsy NN<CC! D   -(a003II J JJJJJJJJJJrCpathmodectd5tj|tjtjztjz|}dddn #1swxYwYtj|dS)zbOpen file at `path` using permission `mode` for writing in binary mode and return file object.rNwb)rosr0O_WRONLYO_CREATO_TRUNCfdopen)rirjfds r7_open_with_moderss   HH WT2;3bj@$ G GHHHHHHHHHHHHHHH 9R  s;AAAc t||5}tjtj|d|ddcdddS#1swxYwYdS)Ntimeoutfileheaderszutf-8)encoding) _fetch_urljsonloadio TextIOWrapperget_content_charset)urlrvresponses r7_fetch_json_syncrs C ) ) ) Xy   !),@@II                       sAA&&A*-A*rrfct}|rg|rUtd||||tdSdSdS)Nz>Network error for %s via IPv6 IP %r, disabling IPv6. Error: %r)rOis_ipv6_enabledlast_ip_was_ipv6rHr_last_ip disable_ipv6rJ)rrfchoosers r7_disable_ipv6_on_network_errorrsG  !W%=%=%?%?! L  OO            !!!!rC)on_error max_triescZKtj} |dt||d{VS#tt jf$r(}td||d}~wtj $r7}t||td|d}~wt$r7}t||td|d}~wt$r}td|d|d}~wtjjt"jjf$r8}t||td||d}~wt($r*}t||td|d |d}~wwxYw) zDownload and decode JSON from *url*. Return decoded JSON. Raise UpdateError: * HTTP response status code is not 200; * Unicode or JSON decoding fails; * on time outs during HTTP request; * on other HTTP errors. Nz!json decode error [{}] for url {}request to {} timed outrequest to {} reset%eof error while updating files, url: , err: 8urllib/http error while updating files, url: {}, err: {} Can't fetch , reason: )r`get_event_looprun_in_executorrUnicodeDecodeErrorr{JSONDecodeErrorr[r/socketrvrConnectionResetErrorEOFErrorhttpclient HTTPExceptionurlliberrorURLErrorr3)rrvloopes r7 _fetch_jsonrs  ! # #D=))$0@#wOOOOOOOOO  4 5NNN=DDQLLMMM >AAA&sA...3::3??@@@ ===&sA.../66s;;<<<     CC C C C C    K %v|'< =   &sA... F M MQ     ===&sA...;;;;;<<<=sQ":F*#A33F*2B77 F*2C66 F*D(F*3E33 F*%F%%F*rxrvc tj|dtjpdi|d}t ||5}|j|jfcdddS#1swxYwYdS)z>Perform HEAD http request to *url* with *timeout* & *headers*.Imunify-Server-IdHEAD)rxmethodruN) rrequestRequestr get_server_idrRr0coderx)rrvrxreqrs r7_perform_http_head_syncrs .  !9!;!;!Ar   !  C     sG  4 4!vqy !!!!!!!!!!!!!!!!!!sA88A<?A< current_mtimecK|turdSt|d} tt||d|id{V\}}|dkrt d|d|t t 5t|d }||kr$t d ||d ||dddn #1swxYwYdS#tj $r7}t||t d |d}~wt$r7}t||t d |d}~wt jjt&jjf$rY}t-|d r|jdkrYd}~dSt||t d||d}~wwxYw)zCheck if we need to download description.json file: - perform HEAD request if local file exists and older return True otherwise return False T)usegmtzIf-Modified-SincerNzUnexpected http code z for z Last-ModifiedaGot code %r, but last modification date %s is earlier than or equal to the date provided in the If-Modified-Since header, the origin server SHOULD generate a 304 (Not Modified) response [rfc7232]. Here's curl cmd: curl -s -I -w '%%{http_code}' -H 'If-Modified-Since: %s' '%s'rrri0Fr)_NEVERrr rr[r Exceptionr timestamprHr_rrvrr/rrrrrrrhasattrr)rrrvformatted_mtimerrx last_mtimers r7_need_to_downloadrst t<<AAA&sA...3::3??@@@ ===&sA.../66s;;<<< K %v|'< =   1f   !&C--55555&sA... F M MQ      sH#C2ACCCG,2D G+2E(GG&3GGT)compressc#Ki}|||d<dtjpdi}|r|dditj||}t j|fi|5}t5}|j ddk}|rl|sj|j d d krLt d |j d|j ||r#|t| n||j d Vdddn #1swxYwYddddS#1swxYwYdS)zs Fetch *url* as binary file. If *compress* is true, ungzipping is done automatically if necessary. NrvrrzAccept-EncodinggziprContent-Encodingz Content-Typezapplication/zipzRequested gzip but got Content-Encoding=%r. Read response as is [identity]. Headers: %s, as curl cmd: curl -Is -H 'Accept-Encoding: gzip' '%s')fileobj)rwrx)rrupdaterrrrRr0rrxgetrHrNitems enter_contextr ) rrvr parameters req_headersrrstackgzippeds r7rzrzQs7J ' 9& (@(B(B(HbIK8-v6777 . k : :C          9;; "'"&&'9::fD    $$^448III KKJ $$%788 &&((    ##HX$>$>$>???'      %                                 s77E(B>E E(E E(E E((E,/E, dest_filec0tj}|}t|||5}|dt x}rL|||||dt x}Ldddn #1swxYwY|dddks||z } |ddd} | Nt| } | | kr9tj d | | | z d |} || |krtd |d |d | | S)z Fetch *url* to *dest_file* and return its md5sum. Raise *urllib.error.ContentTooShortError* if the downloaded file has unexpected length. )rvrrwNrxrrzContent-Lengthz&{got} bytes read, {diff} more expected)gotdiff)messagecontentzcontent fetched from z does not match hash: expected=z, got=)hashlibmd5tellrzr1_BUFSIZErwriterintrrContentTooShortErrorr/ hexdigestr[) rrrvrmd5sumrinitial_file_offsetrchunk file_lengthcontent_length_headerexpected_file_length got_md5sums r7_fetch_n_md5sum_urlr~s +--C#..** C8 < < <#',,X666e # JJu    OOE " " " ',,X666e ################ I  " "#5 6 6& @ @ nn&&)<< !) 3 7 78H$ O O ,#&'<#=#= #{22l77DKK'1K?L! 8J jF22 4C 4 4 4 4'1 4 4    sA/B55B9<B9rgc(Kdt|vS)NzHTTP Error 404)strres r7$_fetch_and_save_should_retry_handlerrs 3s88 ++rC)rr should_retryrr dest_path dest_modec 0K t||5}tt|||||d{VcdddS#1swxYwYdS#tj$r7}t ||t d|d}~wt$r7}t ||t d|d}~wt$r*}t ||t d|d|d}~wtj j tjjf$r8}t ||t d||d}~wt $r-}t ||t d|d |d |d}~wwxYw) zFetch bytes from `url`, save them to `dest_path`, and return md5 checksum of downloaded content. Raise UpdateError: * HTTP response status code is not 200; * on time outs during HTTP request; * on other HTTP errors. rNrrrrrrz to r)rsr rrrvrr[r/rrrrrrrrr3)rrrvrrrrrs r7_fetch_and_saversT0L Y 2 2 i"#!                    >AAA&sA...3::3??@@@ ===&sA.../66s;;<<<    &sA... CC C C C C    K %v|'< =   &sA... F M MQ     LLL&sA...JJJ)JJqJJKKKLsiA A AAAA AF2B F2C F%D(F(3E F((FF_Itemrdatac&d|dDS)z,Return a set of _Item for easy manipulation.cFh|]}t|d|dS)rr)r).0items r7 z_items..s* I I I4E$u+tH~ . . I I IrCrrB)rs r7_itemsrs I I4= I I IIrCcd}|||tj|D]d\}}}|D],}|tj|||-|D],}|tj|||-edS)zCheck and change file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm c tj|jdz}||krmtj|sPt d|t|t|tj||dSdSdS#t$rt d|YdSwxYw)NizGFixing wrong permission to file/dir %s [%s] expected [%s] (not symlink)z&Failed to change permission to file %s) rmlstatst_moderiislinkrHr_octchmodPermissionErrorr) file_dir_path permission current_modes r7 _os_chmodz"check_mode_dirs.._os_chmods 8M22:UBLz))"'..33);! %% OO  33333*)))    LL8-       sB B%B>=B>N)rmwalkrijoin) dirnamedir_perm file_permrridirsfiles directorynames r7check_mode_dirsrs&Igx   WW--;;dE ? ?I Ibgll433X > > > > ; ;D Ibgll4.. : : : : ;;;rCdescription_path files_pathc||jvsJ|j}|}||krR|r/|d|dddS|j}||kPdSdS)aP Try to fix the structure of /var/imunify360/files/ when NotADirectoryError happens. It indicates that some part in the path is a file: /var/imunify360/files/sigs <- is a file => open("/var/imunify360/files/sigs/v1/description.json") will fail. We try to rectify it by deleting the file but up to FILES_DIR. T) missing_ok)parentsexist_okN)r parentis_fileunlinkmkdir)rr_dir topmost_dirs r7_fix_directory_structurers )1 1 1 1 1  "DK *   <<>>  KK4K ( ( (   dT  : : : E{ *      rCceZdZeejZeeZiZ iZ eZ eZ iZ dZedezejZdDdZdZdZdejd d fd Zdejfd Zd Zeddddedededededed d fdZ ed efdZ!ed e"efdZ#eded efdZ$dEd efdZ%d efdZ&d e"efdZ'edZ(d e)efdZ*dZ+e,fd e-fd Z.d efd!Z/d"e-d efd#Z0dEd$Z1de2j3d%e"e4d d fd&Z5d'e"e4d e6e"e4e"effd(Z7eded efd)Z8eded efd*Z9e:dEd+Z;e:d,e2j3d e2j3fd-Zd e?e@e?eeezfffd2ZAd eBefd3ZCdFd4ZDd5e2j3d,e2j3d eEe2j3fd6ZFd efd7ZGe:d8ejd9ejd:e"ed d fd;ZHdZJdFd?ZKdEdFd@ZLe dGdAeEed d fdBZMeded d fdCZNd S)HIndexz/static)periodTcX||jvrtd|d|j||_d|_dgi|_|} t |5}tj||_dddn #1swxYwYn#t$rEt d|ttj|tYnTt t"tjf$r6}|r#t'd||d|_Yd}~nd}~wwxYw|rX|}t-|r5t'd d ||js|dSdS) z :param bool integrity_check: check if last update did not break anything (by interrupting it in the middle or another programmatic error) :raise IntegrityError: z*Trying to initiate unregistered file type z. Allowed types FrNzPath %s has a file in parentszcannot read description file {}Tz'some files are missing or corrupted: {}z, )_TYPES ValueErrortype _is_blank_json_descriptionfile_pathr0r{r|NotADirectoryErrorr_throttled_log_errorrpathlibPath FILES_DIRFileNotFoundErrorrrrTr/_corrupted_fileslenrr)selftype_integrity_checkrir6r bad_filess r7__init__zIndex.__init__1s)  # #(U((+((  r] ))++ "d *q!Yq\\  * * * * * * * * * * * * * * *! D D D  & &'F M M M $W\$%7%7 C C C C C     " " "  $5<}>)r-rVrrr%rr&s r7__repr__zIndex.__repr__ds\ '       4::<<((    rCrr&Nctd|j|||}||jddS)zjWhether *files_path* dir may be used for this type's file group. :raises: IntegrityError zValidating [%s]: %sTr(N)rHrNr_make_file_groupr&r FileGroups r7validatezIndex.validatelsT  )49jAAA))     $)T222222rCc6Gfddj}|S)zV Return FileGroup class: Index class with local path == *files_path*. c6eZdZededeffd ZdS))Index._make_file_group..FileGroupr'r&cF|jksJtjSz+Return local base path for given file type.)rrmfspath)clsr'rr&s r7rz4Index._make_file_group..FileGroup.files_path~s( ))))y,,,rCN)rVrWrX classmethodrr)rr&sr7r8r<}sP  -s -s - - - - - -[ - - -rCr8)r-r7s`` r7r6zIndex._make_file_groupxsG  - - - - - - - - - - -rCc .tj|j}ttjtjttj |jtj |d|ddS)Ndirrw) r_PERMSrrrmrinormpathrr"_PATHSpardir)r&permss r7rzIndex.check_mode_dirssq TY' G   Y TY(?KK   %L &M      rCFall_zip essentialr' relative_pathrrrJrKc|j||r|j|||j|<||d|j|<||j|<dS)aAdd a type to known file types. * relative_path is a relative path to all files for that type. * dir_perm is permission mask used to create directories. * file_perm is permission mask used to create files. * all_zip is a flag which shows whether that type of files can be downloaded in all.zip archive. all.zip is expected to be on the server. * essential is whether the agent can start if there are errors updating that type. )rCrwN)radd_ESSENTIAL_TYPESrFrD_ALL_ZIP_SUPPORT)r@r'rLrrrJrKs r7add_typezIndex.add_typesj, u  ,  $ $U + + +) 5$,i@@ 5&-U###rCcBKtd|jDS)zuWhether essential files exist. Note: the files may be corrupted (integrity check is not performed). c3DK|]}t|dj VdS)Fr5N)rr)rr's r7 z.Index.essential_files_exist..sI  eU333= =      rC)allrOr@s r7essential_files_existzIndex.essential_files_exists9  -      rCc4|jS)z&Return a set of all known files types.)rcopyrVs r7typesz Index.typessz   rCcbtjt|j|Sr>)rmrirr"rFr@r's r7rzIndex.files_paths!w||Isz%'8999rCc |rJ|jtjjvr2tj|dStj||jdS)z9Return local path for description.json for current index.description.json) rr FilesUpdateDISABLEDrmrir_descriptionfile_path_latestr)r&latests r7rzIndex._descriptionfile_pathsm  di6#5#>>>7<<11335G w||DOODI668JKKKrCcrd|Dd}|S)Nc.g|]}|d |dS)rbrCrB)rxs r7 z6Index._descriptionfile_path_latest..s%JJJAakJqxJJJrCr) _get_listvalues)r&ltss r7raz"Index._descriptionfile_path_latests5JJ!1!1!8!8!:!:JJJ1M rCcRt}t|jD]}||j} t |t jt}n%#t$r| |Y_wxYw||j kr| ||S)z9Return a set of file paths that are missing or corrupted.) setrr localfilepathrrrrrr#rNr)r&r)rriactuals r7r$zIndex._corrupted_filessEE 4:&& $ $D%%dh//D "4h??$    d### $$ d###s A!!BBc|j|S)z` usage example: >> async with Index.locked(WHITELISTS): ... )_lockr\s r7lockedz Index.lockedsyrCcDfdtjDS)z(Return iterable over all files in index.c3LK|]}|jVdSr,rlrrrr&s r7rTzIndex.files..s3LL""48,,LLLLLLrC)rrr2s`r7rz Index.filess'LLLL 9K9KLLLLrCc|jdS)z+Return 'items' field from JSON description.r)rr2s r7rz Index.itemssz'""rCc tj|djS#t$r|cYSwxYw)zBReturn mtime of description file if it exists, otherwise -math.infTrb)rmstatrst_mtimer3)r&defaults r7_descriptionfile_mtimezIndex._descriptionfile_mtimesO 7455T5BBCCL L   NNN s ,/ >>c|}|sdS|tjjzt jkS)z4Return True if last update was too late in the past.T)r{rr_PERIODtime)r& _desc_mtimes r7 _is_outdatedzIndex._is_outdateds<1133  4V/66DDrCrvcK|jpyt|dkpT|o@t ||j||d{VS)z>Return True if update from server is needed for current index.rN)rr%r$rr_descriptionfile_urlrr{)r&rvs r7is_update_neededzIndex.is_update_neededs N 4((**++a/ !!##+--di88//11 rCc td5tj|||ddddS#1swxYwYdS#t$r"}t t ||d}~wwxYw)z)Create local directory for current index.r)rjr N)rrmmakedirsr3r[r)r&rdir_moder rs r7 _makedirszIndex._makedirs s -"" G G G(XFFFF G G G G G G G G G G G G G G G G G G - - -c!ff%%1 , -s2A6 A:A:A A/ A**A/ to_updatecK||}||jd}|j|jd}|j|jd}|D]}||j} t j| } t j| s| | |dt|j| |||j d{VdS)zX Fetch files from *to_update* set, verify hashes, save to *files_path*. Fr5rCrwr )rrN) r6rrDrlrrmririsdirrrr) r&rrrvr8fgr file_moderfilenamers r7 _update_fileszIndex._update_filess ))   Yty% 8 8 89RW%e,Ibg&v.   D''11Hgooh//G7==)) Bw5AAA!#{           rC remote_itemsctj}fd|D}fd|D}||z }fd|D}||z }||fS)zFigure out what should be updated based on current items, file system state and remote items. Return tuple of files to fetch and files to delete. Files to fetch is a set of _Item. Files to delete is a set of file paths.cDh|]}|jSrBrsrts r7rz+Index._calculate_changes..7s)LLLt))$(33LLLrCcDh|]}|jSrBrsrts r7rz+Index._calculate_changes..8s)NNN**4844NNNrCcLh|] }|jv|!SrBrs)rrr)r&s r7rz+Index._calculate_changes..<s>   !!$(++9<< <<J#>???rCc Ktj||j}t|}||jdz}|j|jd}|j|jd}| |j}t5}| ||d| tj ||dt||||dd {V} tj|d 5} t#| |d d d n #1swxYwYt%j|D]v\} } } | D]5}t%jt$j| ||6| D]5}t%jt$j| ||6w|||||}nX#t2t4t6t8tjtjf$r"}t?tA||d }~wwxYw|!d d d n #1swxYwYt ||tE||jtFj$j%vS) a Update current type of files using all.zip archive. Directory with current type of files will be cleared and replaced with all.zip contents. all.zip is expected to be on the server Return whether updated. :param timeout: :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) all.ziprwrCFrT)r)rrNr)&r r!rrrrrrrDrrrcallbackrrzipfileZipFile_safe_extractallrmrrrirr9_replace_live_with_new_dirrrTr3r BadZipfile LargeZipFiler[rpop_allboolrr_r`)r&rvrnew_path archive_pathrr all_zip_urlrollback_stack_archiveroot directories filenamesrrold_pathrs r7_run_update_all_zipzIndex._run_update_all_zipesTL!;!;<< ++I66))(-)*CDD K *62 ;ty)%0'' 22 [[1 %N NN8XN > > >  # #&! $   &# A 1_\37787$Wh777888888888888888 57GH4E4EJJ0D+y%0JJ dI!>!>IIII$-JJdH!=!=yIIIIJ h''' ::8YOO"$  1 1 1"#a&&))q0 1  " " $ $ $c1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %h  lh    y 2 ;;;s\>AJH *E; H E H E B9H J 3I<IIJJJversionforcecKtj||j}|st d|jd||dkrY|}d|Dd}t d|j|| d} t|d z t|kr|st d |d |jn(#t$rt d|jd |wxYw|jD]}|s|rt|d z t|krh||jd z}||d|||dd{VdSt d |d|j)a Update to the version specified in *version*. :param version: version to update to :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) zCannot update z, because it is not a symlink: rbc(g|]\}}|d |SrwrB)rverprops r7rfz#Index.update_to..s5!ThrCrz%Try to update to latest version %s %sFstrictVERSIONzVersion z is already set for z5, because current version doesn't have VERSION file: liveTtarget_is_directory is_updatedNz not found in )r r!rr is_symlinkr[rgrrHrNresolver read_textr2r#r iterdiris_dirrr symlink_torename _run_hooks)r&rrrversions current_pathri new_live_paths r7 update_tozIndex.update_tosL!;!;<< ##%% +999ii)  h  ~~''H%-^^%5%5G KK7G   !(((66   1<<>>DDFFGG7##$$$"GwGGDIGG!   +'+yyy,,@   $,,..  D??$$  D9,7799??AABBgGG%)NN49v3E$F$FM!,,Tt,LLL!((333//T/:::::::::FFk'''499MNNNs A#D77%Ectj||j}|siS|d}i}t d}|jD]}|r||krd}nd}|dz x} rqd} | }t |} |dt|d|| <| |kr| }#t$rtd||YwxYw|t dkr d||d<|S) NFrr,Tr)currentrbrCz Version file %s is not valid: %srb)r r!rrrrr r rrArrrrHr) r&rrresult max_versionrir version_filer_vers r7rgzIndex._get_listsL!;!;<< ##%% I (((66 cll $,,..  D   ||~~-- $y 00 88:: *4466G"7++D#*"'"4yy$$F4L k))&* !LL:$ H  &  % %,0F;  ) sAD&D>=D>cg}t|dD]4\}}|drdn |drdnd}|||5|S)z/Return list of versions available in the index.c|dS)NrrB)res r7z Index.get_list..s AaDrC)keyrz (current)rbz (latest)r)sortedrgrappend)r&rrrmarkers r7get_listzIndex.get_lists# NN   " " $ $..   0 0MGT  ? >[[  MMW.f.. / / / / rCctj||j}|sdS|d}|jD]}tj tj j tj |jtj j z j}|rf|sR||krL|t$jjkr7t*d|j|t/j|ddS)z~Remove old versions of files. This is done by removing old directories in the path, that older than 30 days. NFrzRemoving old version of %s: %sTr)r r!rrrrr rrrnowtimezoneutc fromtimestamprxrydaysrrr_ DAYS_TO_KEEPrHrNrr)r&rrridays_olds r7_clean_old_versionszIndex._clean_old_versions s6 L!;!;<< ##%%  F (((66 $,,.. 8 8D  00+++IIKK("+/   8)) 8L(( 2 ??? &&  9%&) "))*555'// 0BINNN-O0% j====  " " $ $ $[- %- %- %- %- %- %- %- %- %- %- %- %- %- %- %^s8=A/G -DG B FG F/G  GGc Kj}t||d{V}t |\}}|p|}|s6t djdStj j}| r| dnd}t|} t5} | jjdd| t&j| d |r|r|n|} | rAt| | |fd |Dd{V| ||d{V t3| d z jjd 5} | t7j|dddn #1swxYwY| | |}n6#t@tBf$r"} tEtG| | d} ~ wwxYw| $dddn #1swxYwY|rE|r1t d |t'j|d jtJj&j'vS)z Run update, return whether updated. :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) ruNzupdating %s: nothing to update.FrrCrTrc3LK|]}|jVdSr,rsrts r7rTz$Index._run_update..sD$$9=**4844$$$$$$rCr^rwz,Removing old path on file by file update: %s)(rrrrrrHrN_touchr r!rrrrrrrrDrrrr _copytreeunionrrsrr{dumpsencoder9rrTr3r[rrrr_r`)r&rvras_jsonrr need_updaterrrr from_pathrwrs` r7 _run_updatezIndex._run_updates'' 22#C999999999#66vgGG 9,9   KK949 E E E KKMMM5L!;!;<< 09/C/C/E/E OI  U  + + +4 ++I66[[* %N NN$+di07%      # # xt $   %I):):I  !! ooOO$$$$AJ$$$$$Xy'$JJ J J J J J J J 1$11K *62=JJtz'2299;;<<< =============== h''' ::8YOO"G, 1 1 1!#a&&))q0 1  " " $ $ $U* %* %* %* %* %* %* %* %* %* %* %* %* %* %* %Z  8)) 8 KK>    M($ 7 7 7 7y 2 ;;;s[CK15)J:I$ J$I( (J+I( ,.JK1K,K  KK11K58K5from_dirto_dir ignored_pathsc`Kfd}ttj||d|dd{VdS)z7Copy *from_dir* to *to_dir* except for *ignored_paths*.cttjtsJt fd|DS)z(Return names that should not be copied.c3`K|](}tj|v$|V)dSr,)rmrir)rrr ris r7rTz8Index._copytree..ignore_names..sJ7<<d++}<<<<<<rC) isinstancermr?r frozenset)rinamesr s` r7 ignore_namesz%Index._copytree..ignore_namess_bioos33 3 33! rCT)symlinksignore dirs_exist_okN)r rcopytree)r r r rs ` r7rzIndex._copytreesu       O               rCrctjt|j|j}|j|j}tj|tj|j vsJd ||tj||}tj | |j|S)z.Return a local file path corresponding to URL.z$url ({}) does not fit file path ({})) rmrirelpathr_URL_PATH_PREFIXrFrr r!r r/rr)r&r url_relpath type_pathrLs r7rlzIndex.localfilepathsgoo SMM  5  K * L # #w|K'@'@'H H H H 1 8 8i H H I H H Y?? w||DOODI66 FFFrCc |d}tj|rtj|dSdS#t $r2}t t|Yd}~dSd}~wwxYw)z5Update mtime of description.json file so it is fresh.TrwN) rrmriisfileutimer3rHr_r)r&rirs r7rz Index._touchs #--T-::Dw~~d##    # # # NN3q66 " " " " " " " " " #sA A B 'BB cKt|j|jtgD]}} |||d{V#tt f$r&}t d||Yd}~Hd}~wt$r&}t d||Yd}~vd}~wwxYwt d|jd| zdS)Nzhook %s error: %sz%s files update finished%sz (not updated)) r _HOOKSrr%rTrrHrr exceptionrN)r&rhookrs r7rzIndex._run_hookss$+di0<.AA ? ?D ?d4,,,,,,,,,,"N3 ; ; ; 0$:::::::: ? ? ?  !4dA>>>>>>>> ? ( I J /     s!?B$A11 B$>BB$c$Ktjj}|sy||d{Vs^td|jttjjdz| dd{VdS|j o|j |j}| }|rd}td|j| tj |tjj|d{V}|r!td|j|nG#tjt"f$r.}td |j||d }Yd}~nd}~wwxYw|rd }td|j| tj |tjj|d{V}|r!td|j|nr#tjt"f$rY}td |j||| dd{V|j|jvr|Yd}~dSd}~wwxYw| |p|d{VdS) aRun update for the current `type` of files. Normally update is performed when either is true: * index is never been fetched (description.json missing or broken); * last update was performed longer than configured period of time ago; * some local files are missing or have wrong content (md5 hash differs from description.json). If force is True then update is performed unconditionally. Raises asyncio.TimeoutError, UpdateError. Nz(%s was updated less than %s minutes ago.<FrrzUpdating %s files via %szUpdated %s using %sz%s update error via %s: %sTzfile by file download)rr_TIMEOUTrrHrNrrr}rrrPr`wait_forrSOCKET_TIMEOUT TimeoutErrorr[r_r rO)r&rrvrJ file_by_filelog_strupdatedrs r7rz Index.update s$, 4#8#8#A#AAAAAAA  KK: F&-344    //U/33 3 3 3 3 3 3 3 F.ET%:49%E"{  $G KK2DIw G G G $ ' 0,,*9 !! KKK 5ty'JJJ(+6 $ $ $0$)Wa $  $  -G KK2DIw G G G  ' 0$$V%7%FGG!!KKK 5ty'JJJ(+6   0$)Waooo7777777779 555GFFFFF oo)9Eo:::::::::::s3A D22E6$E11E6A HI/AI**I/ only_typecK|rj||d}||4d{V||d{Vdddd{VdS#1d{VswxYwYdS|rtd|jD]i}||d}||4d{V||d{Vdddd{Vn#1d{VswxYwYjdStd|jD]i}||d}||4d{V||d{Vdddd{Vn#1d{VswxYwYjdS)zkRun update for all registered `types` of files. Raises asyncio.TimeoutError, UpdateError. Fr5NzUpdating essential fileszUpdating all files)rprrHrNrOr)r@r-ronly_essentialindexr's r7 update_allzIndex.update_allOss  .C 5999Ezz),, * * * * * * * *ll5))))))))) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  . KK2 3 3 3- . .E5999::e,,........,,u---------........................... . . KK, - - - . .E5999::e,,........,,u---------........................... . .s5A A&)A&<C** C4 7C4 E77 F F cF|j||dS)z:Add a hook for type_ to be called after successful update.N)r!rN)r@r'r#s r7add_hookzIndex.add_hookhs% 5d#####rC)T)Fr&N)NFF)OrVrWrXrr`Lockrorkr!rFrDrrOrPrrrrHrrr*r/r3rmPathLiker9r6rrArrrrQrWrrZrrrar$rprrrrfloatr{rrrr r!rrrrrr staticmethodrrrrrr rgrrrrrr rrlrrrr1r3rBrCr7rr$s K % %E [  F F F SUUFsuu 6::QX666v|DD)#)#)#)#V       32; 34 3 3 3 3 2;       ..... .  ... ...[.8  D    [  !c#h!!![!:s:s:::[:LLSLLLLc #c(      [ Mx}MMMM###.4EdEEEE  e       ----!,36u: 2$J$ s5z3s8# $$$$$.KKKKK[KBBBBB[B : : :\ :@gl@w|@@@\@O >?%%%%N$s) 88884C C18C ', CCCCJNr"rr_MAX_TRIES_FOR_DOWNLOADrdinfrrrr7JSONTyper=__annotations__r>rDrJrOrR RuntimeErrorrTr[rhr6rsrrrrrrrzrrrrrrrrr?r1rrWrArCrBrCr7r^s  ////////::::::::::99999999%%%%%%                      "!!!!!-,,,,,888888======EEEEEEEEEE99999999333333NMMMMM 8  #w|$DEEA: $4 %   GL0 1 1 1  ( c5$d38nd3iG H8< X3 4;;;%) H\ ")))GdGGGGMMMM 6     PPPPP\PPP     ,   KKK"+SX h     ! !) ! ! ! ! ! ,8O"=3"=H"="="="=L*, ! ! !  ! ! ! ! !  ,8O7 7"7-27 7777t59) ) ) C) U) ) ) ) X&&&&&R,I,#,$,,,,  %5  0L0L0L 0L{0L  0L 0L0L0L  0Lf  7UH-..JJUJJJJ ;;;F@Il07  ,G $G $G $G $G $G $G $G $TOOOO,  3,1   }             rCdefence360agent/files/__pycache__/__init__.cpython-311.pyc0000644000000000000000000022225400000000000020330 0ustar r_j PUdZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl Z ddlZddlZddlZddlZddlZddlmZmZddlmZmZmZddlmZmZddlmZddl m!Z!ddl"m#Z#dd l$m%Z%dd l&m'Z'm(Z(m)Z)m*Z*m+Z+m,Z,m-Z-m.Z.m/Z/dd l0m1Z1dd l2m3Z3dd l4m5Z5ddl6m7Z7ddl8m9Z9m:Z:m;Z;ddlZ>ddl?m@Z@ddlAmBZBmCZCddlDmEZFddlGmHZHe#eIZJe jKdZLdZMdZNdeOfdZPdZQdZRdZSdZTdZUe jKd ZVd!ZWd"ZXd#ZYd$ZZ e j[ Z\e/e]e^e_eOde)e]e'fe+e'fZ`daae,eCebd%<dace,eBebd&<deOfd'ZddYd(ZedeCfd)ZfdeBfd*ZgGd+d,ehZiGd-d.ehZjd/Zkd0e jld1e^de(fd2Zmde`fd3Znd4e]d5eoddfd6Zpe:ejekeY7d4e]de`fd8Zqid9d4e]d:e_fd;Zre:ejekeY7d4e]dd?d4e]d:e_fd@ZtdAe(fdBZud5eodCe^deOfdDZve:ejekeYevEd>ddFd4e]dGe jldHe^de]fdIZwedJd4dKgZxdLe'de-exfdMZydNZzeVfdOe jKdPe jKddfdQZ{GdRdSZ|dYdTZ}e|j~Ze|jZ dZdVe,e]ddfdWZdXZdS)[aPUtilities for managing local file storage synchronised with a remote server. Files are divided into types: signatures, modsecurity bundles, ip white lists, etc. Each type is represented by an Index instance. Index has a local subdirectory and a description that contains its files' metadata used to decide if the update is necessary. N) defaultdict namedtuple) ExitStacksuppresscontextmanager) formatdateparsedate_to_datetime)GzipFile)chain) getLogger)Version) AnyBinaryIODictIterableListOptionalSetTupleUnion)urlparse)config) LicenseCLN)PanelException) file_hashretry_onrun_with_umask) rate_limitHOUR) to_thread) UrlTransportRandomIpChooserWithIPv6Toggle)safe_extractall) default_hookz/var/imunify360/.ipv6_disabledz(/proc/sys/net/ipv6/conf/all/disable_ipv6z(/sys/module/{mod}/parameters/{parameter}returnctdd} t|5}|dkr ddddS dddn #1swxYwYn#t $rYdSwxYw tt 5}|dkr ddddS dddn #1swxYwYn#t $rYnwxYwdS) zCheck whether IPv6 is disabled at the kernel level. Reads the module parameter and the runtime sysctl without depending on the im360 package. ipv6disable)mod parameter0NT1F) _MOD_PAR_PATHformatopenreadstripOSError_SYSCTL_DISABLE_IPV6) param_filefs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/__init__.py_is_kernel_ipv6_disabledr8Ds %%&I%FFJ *   vvxx~~3&&        &                tt & ' ' 1vvxx~~3&&        &                      5svA?,A3 A?' A?3A77A?:A7;A?? B  B C7%,C+ C7 C7+C//C72C/3C77 DDeulasigszrealtime-av-confzwp-rulesgeoz/var/imunify360/filesz$https://files.imunify360.com/static/i g? _IP_CHOOSER _TRANSPORTcPtptS)zCheck if IPv6 should be disabled at startup. True when either the kernel has disabled IPv6 or a previous agent run persisted the disabled state after a runtime network failure. )r8_IPV6_DISABLED_STATEexistsr7_should_disable_ipv6rD~s" $ % % F)=)D)D)F)FFrCc tdS#t$r tddYdSwxYw)z:Persist IPv6 disabled state so it survives agent restarts.z%Could not persist IPv6 disabled stateT)exc_infoN)r@touchr3loggerdebugrBrCr7_persist_ipv6_disabledrJs_M""$$$$$ MMM r!rOrBrCr7_get_transportrRs$!_->->??? rCceZdZdZdS)IntegrityErrorzERaised when on disk content does not match hashes in description.jsonN__name__ __module__ __qualname____doc__rBrCr7rTrTsOOOOrCrTceZdZdZdS) UpdateErrora Raised on other errors during files update. Possible reasons are: * server returns non 200 status; * hash mismatched between downloaded content and description.json; * urllib errors; * JSON decoding errors; * errors while writing to disk. NrUrBrCr7r[r[s    rCr[cKtd||tjt jd|ztzd{VdS)Nz2Files update failed with error: {err}, try: {try_})errtry_r$)rHwarningr/asynciosleeprandom randrange_TIMEOUT_MULTIPLICATORexcis r7_log_failed_updaterhsy NN<CC! D   -(a003II J JJJJJJJJJJrCpathmodectd5tj|tjtjztjz|}dddn #1swxYwYtj|dS)zbOpen file at `path` using permission `mode` for writing in binary mode and return file object.rNwb)rosr0O_WRONLYO_CREATO_TRUNCfdopen)rirjfds r7_open_with_moderss   HH WT2;3bj@$ G GHHHHHHHHHHHHHHH 9R  s;AAAc t||5}tjtj|d|ddcdddS#1swxYwYdS)Ntimeoutfileheaderszutf-8)encoding) _fetch_urljsonloadio TextIOWrapperget_content_charset)urlrvresponses r7_fetch_json_syncrs C ) ) ) Xy   !),@@II                       sAA&&A*-A*rrfct}|rg|rUtd||||tdSdSdS)Nz>Network error for %s via IPv6 IP %r, disabling IPv6. Error: %r)rOis_ipv6_enabledlast_ip_was_ipv6rHr_last_ip disable_ipv6rJ)rrfchoosers r7_disable_ipv6_on_network_errorrsG  !W%=%=%?%?! L  OO            !!!!rC)on_error max_triescZKtj} |dt||d{VS#tt jf$r(}td||d}~wtj $r7}t||td|d}~wt$r7}t||td|d}~wt$r}td|d|d}~wtjjt"jjf$r8}t||td||d}~wt($r*}t||td|d |d}~wwxYw) zDownload and decode JSON from *url*. Return decoded JSON. Raise UpdateError: * HTTP response status code is not 200; * Unicode or JSON decoding fails; * on time outs during HTTP request; * on other HTTP errors. Nz!json decode error [{}] for url {}request to {} timed outrequest to {} reset%eof error while updating files, url: , err: 8urllib/http error while updating files, url: {}, err: {} Can't fetch , reason: )r`get_event_looprun_in_executorrUnicodeDecodeErrorr{JSONDecodeErrorr[r/socketrvrConnectionResetErrorEOFErrorhttpclient HTTPExceptionurlliberrorURLErrorr3)rrvloopes r7 _fetch_jsonrs  ! # #D=))$0@#wOOOOOOOOO  4 5NNN=DDQLLMMM >AAA&sA...3::3??@@@ ===&sA.../66s;;<<<     CC C C C C    K %v|'< =   &sA... F M MQ     ===&sA...;;;;;<<<=sQ":F*#A33F*2B77 F*2C66 F*D(F*3E33 F*%F%%F*rxrvc tj|dtjpdi|d}t ||5}|j|jfcdddS#1swxYwYdS)z>Perform HEAD http request to *url* with *timeout* & *headers*.Imunify-Server-IdHEAD)rxmethodruN) rrequestRequestr get_server_idrRr0coderx)rrvrxreqrs r7_perform_http_head_syncrs .  !9!;!;!Ar   !  C     sG  4 4!vqy !!!!!!!!!!!!!!!!!!sA88A<?A< current_mtimecK|turdSt|d} tt||d|id{V\}}|dkrt d|d|t t 5t|d }||kr$t d ||d ||dddn #1swxYwYdS#tj $r7}t||t d |d}~wt$r7}t||t d |d}~wt jjt&jjf$rY}t-|d r|jdkrYd}~dSt||t d||d}~wwxYw)zCheck if we need to download description.json file: - perform HEAD request if local file exists and older return True otherwise return False T)usegmtzIf-Modified-SincerNzUnexpected http code z for z Last-ModifiedaGot code %r, but last modification date %s is earlier than or equal to the date provided in the If-Modified-Since header, the origin server SHOULD generate a 304 (Not Modified) response [rfc7232]. Here's curl cmd: curl -s -I -w '%%{http_code}' -H 'If-Modified-Since: %s' '%s'rrri0Fr)_NEVERrr rr[r Exceptionr timestamprHr_rrvrr/rrrrrrrhasattrr)rrrvformatted_mtimerrx last_mtimers r7_need_to_downloadrst t<<AAA&sA...3::3??@@@ ===&sA.../66s;;<<< K %v|'< =   1f   !&C--55555&sA... F M MQ      sH#C2ACCCG,2D G+2E(GG&3GGT)compressc#Ki}|||d<dtjpdi}|r|dditj||}t j|fi|5}t5}|j ddk}|rl|sj|j d d krLt d |j d|j ||r#|t| n||j d Vdddn #1swxYwYddddS#1swxYwYdS)zs Fetch *url* as binary file. If *compress* is true, ungzipping is done automatically if necessary. NrvrrzAccept-EncodinggziprContent-Encodingz Content-Typezapplication/zipzRequested gzip but got Content-Encoding=%r. Read response as is [identity]. Headers: %s, as curl cmd: curl -Is -H 'Accept-Encoding: gzip' '%s')fileobj)rwrx)rrupdaterrrrRr0rrxgetrHrNitems enter_contextr ) rrvr parameters req_headersrrstackgzippeds r7rzrzQs7J ' 9& (@(B(B(HbIK8-v6777 . k : :C          9;; "'"&&'9::fD    $$^448III KKJ $$%788 &&((    ##HX$>$>$>???'      %                                 s77E(B>E E(E E(E E((E,/E, dest_filec0tj}|}t|||5}|dt x}rL|||||dt x}Ldddn #1swxYwY|dddks||z } |ddd} | Nt| } | | kr9tj d | | | z d |} || |krtd |d |d | | S)z Fetch *url* to *dest_file* and return its md5sum. Raise *urllib.error.ContentTooShortError* if the downloaded file has unexpected length. )rvrrwNrxrrzContent-Lengthz&{got} bytes read, {diff} more expected)gotdiff)messagecontentzcontent fetched from z does not match hash: expected=z, got=)hashlibmd5tellrzr1_BUFSIZErwriterintrrContentTooShortErrorr/ hexdigestr[) rrrvrmd5sumrinitial_file_offsetrchunk file_lengthcontent_length_headerexpected_file_length got_md5sums r7_fetch_n_md5sum_urlr~s +--C#..** C8 < < <#',,X666e # JJu    OOE " " " ',,X666e ################ I  " "#5 6 6& @ @ nn&&)<< !) 3 7 78H$ O O ,#&'<#=#= #{22l77DKK'1K?L! 8J jF22 4C 4 4 4 4'1 4 4    sA/B55B9<B9rgc(Kdt|vS)NzHTTP Error 404)strres r7$_fetch_and_save_should_retry_handlerrs 3s88 ++rC)rr should_retryrr dest_path dest_modec 0K t||5}tt|||||d{VcdddS#1swxYwYdS#tj$r7}t ||t d|d}~wt$r7}t ||t d|d}~wt$r*}t ||t d|d|d}~wtj j tjjf$r8}t ||t d||d}~wt $r-}t ||t d|d |d |d}~wwxYw) zFetch bytes from `url`, save them to `dest_path`, and return md5 checksum of downloaded content. Raise UpdateError: * HTTP response status code is not 200; * on time outs during HTTP request; * on other HTTP errors. rNrrrrrrz to r)rsr rrrvrr[r/rrrrrrrrr3)rrrvrrrrrs r7_fetch_and_saversT0L Y 2 2 i"#!                    >AAA&sA...3::3??@@@ ===&sA.../66s;;<<<    &sA... CC C C C C    K %v|'< =   &sA... F M MQ     LLL&sA...JJJ)JJqJJKKKLsiA A AAAA AF2B F2C F%D(F(3E F((FF_Itemrdatac&d|dDS)z,Return a set of _Item for easy manipulation.cFh|]}t|d|dS)rr)r).0items r7 z_items..s* I I I4E$u+tH~ . . I I IrCrrB)rs r7_itemsrs I I4= I I IIrCcd}|||tj|D]d\}}}|D],}|tj|||-|D],}|tj|||-edS)zCheck and change file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm c tj|jdz}||krmtj|sPt d|t|t|tj||dSdSdS#t$rt d|YdSwxYw)NizGFixing wrong permission to file/dir %s [%s] expected [%s] (not symlink)z&Failed to change permission to file %s) rmlstatst_moderiislinkrHr_octchmodPermissionErrorr) file_dir_path permission current_modes r7 _os_chmodz"check_mode_dirs.._os_chmods 8M22:UBLz))"'..33);! %% OO  33333*)))    LL8-       sB B%B>=B>N)rmwalkrijoin) dirnamedir_perm file_permrridirsfiles directorynames r7check_mode_dirsrs&Igx   WW--;;dE ? ?I Ibgll433X > > > > ; ;D Ibgll4.. : : : : ;;;rCdescription_path files_pathc||jvsJ|j}|}||krR|r/|d|dddS|j}||kPdSdS)aP Try to fix the structure of /var/imunify360/files/ when NotADirectoryError happens. It indicates that some part in the path is a file: /var/imunify360/files/sigs <- is a file => open("/var/imunify360/files/sigs/v1/description.json") will fail. We try to rectify it by deleting the file but up to FILES_DIR. T) missing_ok)parentsexist_okN)r parentis_fileunlinkmkdir)rr_dir topmost_dirs r7_fix_directory_structurers )1 1 1 1 1  "DK *   <<>>  KK4K ( ( (   dT  : : : E{ *      rCceZdZeejZeeZiZ iZ eZ eZ iZ dZedezejZdDdZdZdZdejd d fd Zdejfd Zd Zeddddedededededed d fdZ ed efdZ!ed e"efdZ#eded efdZ$dEd efdZ%d efdZ&d e"efdZ'edZ(d e)efdZ*dZ+e,fd e-fd Z.d efd!Z/d"e-d efd#Z0dEd$Z1de2j3d%e"e4d d fd&Z5d'e"e4d e6e"e4e"effd(Z7eded efd)Z8eded efd*Z9e:dEd+Z;e:d,e2j3d e2j3fd-Zd e?e@e?eeezfffd2ZAd eBefd3ZCdFd4ZDd5e2j3d,e2j3d eEe2j3fd6ZFd efd7ZGe:d8ejd9ejd:e"ed d fd;ZHdZJdFd?ZKdEdFd@ZLe dGdAeEed d fdBZMeded d fdCZNd S)HIndexz/static)periodTcX||jvrtd|d|j||_d|_dgi|_|} t |5}tj||_dddn #1swxYwYn#t$rEt d|ttj|tYnTt t"tjf$r6}|r#t'd||d|_Yd}~nd}~wwxYw|rX|}t-|r5t'd d ||js|dSdS) z :param bool integrity_check: check if last update did not break anything (by interrupting it in the middle or another programmatic error) :raise IntegrityError: z*Trying to initiate unregistered file type z. Allowed types FrNzPath %s has a file in parentszcannot read description file {}Tz'some files are missing or corrupted: {}z, )_TYPES ValueErrortype _is_blank_json_descriptionfile_pathr0r{r|NotADirectoryErrorr_throttled_log_errorrpathlibPath FILES_DIRFileNotFoundErrorrrrTr/_corrupted_fileslenrr)selftype_integrity_checkrir6r bad_filess r7__init__zIndex.__init__1s)  # #(U((+((  r] ))++ "d *q!Yq\\  * * * * * * * * * * * * * * *! D D D  & &'F M M M $W\$%7%7 C C C C C     " " "  $5<}>)r-rVrrr%rr&s r7__repr__zIndex.__repr__ds\ '       4::<<((    rCrr&Nctd|j|||}||jddS)zjWhether *files_path* dir may be used for this type's file group. :raises: IntegrityError zValidating [%s]: %sTr(N)rHrNr_make_file_groupr&r FileGroups r7validatezIndex.validatelsT  )49jAAA))     $)T222222rCc6Gfddj}|S)zV Return FileGroup class: Index class with local path == *files_path*. c6eZdZededeffd ZdS))Index._make_file_group..FileGroupr'r&cF|jksJtjSz+Return local base path for given file type.)rrmfspath)clsr'rr&s r7rz4Index._make_file_group..FileGroup.files_path~s( ))))y,,,rCN)rVrWrX classmethodrr)rr&sr7r8r<}sP  -s -s - - - - - -[ - - -rCr8)r-r7s`` r7r6zIndex._make_file_groupxsG  - - - - - - - - - - -rCc .tj|j}ttjtjttj |jtj |d|ddS)Ndirrw) r_PERMSrrrmrinormpathrr"_PATHSpardir)r&permss r7rzIndex.check_mode_dirssq TY' G   Y TY(?KK   %L &M      rCFall_zip essentialr' relative_pathrrrJrKc|j||r|j|||j|<||d|j|<||j|<dS)aAdd a type to known file types. * relative_path is a relative path to all files for that type. * dir_perm is permission mask used to create directories. * file_perm is permission mask used to create files. * all_zip is a flag which shows whether that type of files can be downloaded in all.zip archive. all.zip is expected to be on the server. * essential is whether the agent can start if there are errors updating that type. )rCrwN)radd_ESSENTIAL_TYPESrFrD_ALL_ZIP_SUPPORT)r@r'rLrrrJrKs r7add_typezIndex.add_typesj, u  ,  $ $U + + +) 5$,i@@ 5&-U###rCcBKtd|jDS)zuWhether essential files exist. Note: the files may be corrupted (integrity check is not performed). c3DK|]}t|dj VdS)Fr5N)rr)rr's r7 z.Index.essential_files_exist..sI  eU333= =      rC)allrOr@s r7essential_files_existzIndex.essential_files_exists9  -      rCc4|jS)z&Return a set of all known files types.)rcopyrVs r7typesz Index.typessz   rCcbtjt|j|Sr>)rmrirr"rFr@r's r7rzIndex.files_paths!w||Isz%'8999rCc |rJ|jtjjvr2tj|dStj||jdS)z9Return local path for description.json for current index.description.json) rr FilesUpdateDISABLEDrmrir_descriptionfile_path_latestr)r&latests r7rzIndex._descriptionfile_pathsm  di6#5#>>>7<<11335G w||DOODI668JKKKrCcrd|Dd}|S)Nc.g|]}|d |dS)rbrCrB)rxs r7 z6Index._descriptionfile_path_latest..s%JJJAakJqxJJJrCr) _get_listvalues)r&ltss r7raz"Index._descriptionfile_path_latests5JJ!1!1!8!8!:!:JJJ1M rCcRt}t|jD]}||j} t |t jt}n%#t$r| |Y_wxYw||j kr| ||S)z9Return a set of file paths that are missing or corrupted.) setrr localfilepathrrrrrr#rNr)r&r)rriactuals r7r$zIndex._corrupted_filessEE 4:&& $ $D%%dh//D "4h??$    d### $$ d###s A!!BBc|j|S)z` usage example: >> async with Index.locked(WHITELISTS): ... )_lockr\s r7lockedz Index.lockedsyrCcDfdtjDS)z(Return iterable over all files in index.c3LK|]}|jVdSr,rlrrrr&s r7rTzIndex.files..s3LL""48,,LLLLLLrC)rrr2s`r7rz Index.filess'LLLL 9K9KLLLLrCc|jdS)z+Return 'items' field from JSON description.r)rr2s r7rz Index.itemssz'""rCc tj|djS#t$r|cYSwxYw)zBReturn mtime of description file if it exists, otherwise -math.infTrb)rmstatrst_mtimer3)r&defaults r7_descriptionfile_mtimezIndex._descriptionfile_mtimesO 7455T5BBCCL L   NNN s ,/ >>c|}|sdS|tjjzt jkS)z4Return True if last update was too late in the past.T)r{rr_PERIODtime)r& _desc_mtimes r7 _is_outdatedzIndex._is_outdateds<1133  4V/66DDrCrvcK|jpyt|dkpT|o@t ||j||d{VS)z>Return True if update from server is needed for current index.rN)rr%r$rr_descriptionfile_urlrr{)r&rvs r7is_update_neededzIndex.is_update_neededs N 4((**++a/ !!##+--di88//11 rCc td5tj|||ddddS#1swxYwYdS#t$r"}t t ||d}~wwxYw)z)Create local directory for current index.r)rjr N)rrmmakedirsr3r[r)r&rdir_moder rs r7 _makedirszIndex._makedirs s -"" G G G(XFFFF G G G G G G G G G G G G G G G G G G - - -c!ff%%1 , -s2A6 A:A:A A/ A**A/ to_updatecK||}||jd}|j|jd}|j|jd}|D]}||j} t j| } t j| s| | |dt|j| |||j d{VdS)zX Fetch files from *to_update* set, verify hashes, save to *files_path*. Fr5rCrwr )rrN) r6rrDrlrrmririsdirrrr) r&rrrvr8fgr file_moderfilenamers r7 _update_fileszIndex._update_filess ))   Yty% 8 8 89RW%e,Ibg&v.   D''11Hgooh//G7==)) Bw5AAA!#{           rC remote_itemsctj}fd|D}fd|D}||z }fd|D}||z }||fS)zFigure out what should be updated based on current items, file system state and remote items. Return tuple of files to fetch and files to delete. Files to fetch is a set of _Item. Files to delete is a set of file paths.cDh|]}|jSrBrsrts r7rz+Index._calculate_changes..7s)LLLt))$(33LLLrCcDh|]}|jSrBrsrts r7rz+Index._calculate_changes..8s)NNN**4844NNNrCcLh|] }|jv|!SrBrs)rrr)r&s r7rz+Index._calculate_changes..<s>   !!$(++9<< <<J#>???rCc Ktj||j}t|}||jdz}|j|jd}|j|jd}| |j}t5}| ||d| tj ||dt||||dd {V} tj|d 5} t#| |d d d n #1swxYwYt%j|D]v\} } } | D]5}t%jt$j| ||6| D]5}t%jt$j| ||6w|||||}nX#t2t4t6t8tjtjf$r"}t?tA||d }~wwxYw|!d d d n #1swxYwYt ||tE||jtFj$j%vS) a Update current type of files using all.zip archive. Directory with current type of files will be cleared and replaced with all.zip contents. all.zip is expected to be on the server Return whether updated. :param timeout: :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) all.ziprwrCFrT)r)rrNr)&r r!rrrrrrrDrrrcallbackrrzipfileZipFile_safe_extractallrmrrrirr9_replace_live_with_new_dirrrTr3r BadZipfile LargeZipFiler[rpop_allboolrr_r`)r&rvrnew_path archive_pathrr all_zip_urlrollback_stack_archiveroot directories filenamesrrold_pathrs r7_run_update_all_zipzIndex._run_update_all_zipesTL!;!;<< ++I66))(-)*CDD K *62 ;ty)%0'' 22 [[1 %N NN8XN > > >  # #&! $   &# A 1_\37787$Wh777888888888888888 57GH4E4EJJ0D+y%0JJ dI!>!>IIII$-JJdH!=!=yIIIIJ h''' ::8YOO"$  1 1 1"#a&&))q0 1  " " $ $ $c1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %1 %h  lh    y 2 ;;;s\>AJH *E; H E H E B9H J 3I<IIJJJversionforcecKtj||j}|st d|jd||dkrY|}d|Dd}t d|j|| d} t|d z t|kr|st d |d |jn(#t$rt d|jd |wxYw|jD]}|s|rt|d z t|krh||jd z}||d|||dd{VdSt d |d|j)a Update to the version specified in *version*. :param version: version to update to :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) zCannot update z, because it is not a symlink: rbc(g|]\}}|d |SrwrB)rverprops r7rfz#Index.update_to..s5!ThrCrz%Try to update to latest version %s %sFstrictVERSIONzVersion z is already set for z5, because current version doesn't have VERSION file: liveTtarget_is_directory is_updatedNz not found in )r r!rr is_symlinkr[rgrrHrNresolver read_textr2r#r iterdiris_dirrr symlink_torename _run_hooks)r&rrrversions current_pathri new_live_paths r7 update_tozIndex.update_tosL!;!;<< ##%% +999ii)  h  ~~''H%-^^%5%5G KK7G   !(((66   1<<>>DDFFGG7##$$$"GwGGDIGG!   +'+yyy,,@   $,,..  D??$$  D9,7799??AABBgGG%)NN49v3E$F$FM!,,Tt,LLL!((333//T/:::::::::FFk'''499MNNNs A#D77%Ectj||j}|siS|d}i}t d}|jD]}|r||krd}nd}|dz x} rqd} | }t |} |dt|d|| <| |kr| }#t$rtd||YwxYw|t dkr d||d<|S) NFrr,Tr)currentrbrCz Version file %s is not valid: %srb)r r!rrrrr r rrArrrrHr) r&rrresult max_versionrir version_filer_vers r7rgzIndex._get_listsL!;!;<< ##%% I (((66 cll $,,..  D   ||~~-- $y 00 88:: *4466G"7++D#*"'"4yy$$F4L k))&* !LL:$ H  &  % %,0F;  ) sAD&D>=D>cg}t|dD]4\}}|drdn |drdnd}|||5|S)z/Return list of versions available in the index.c|dS)NrrB)res r7z Index.get_list..s AaDrC)keyrz (current)rbz (latest)r)sortedrgrappend)r&rrrmarkers r7get_listzIndex.get_lists# NN   " " $ $..   0 0MGT  ? >[[  MMW.f.. / / / / rCctj||j}|sdS|d}|jD]}tj tj j tj |jtj j z j}|rf|sR||krL|t$jjkr7t*d|j|t/j|ddS)z~Remove old versions of files. This is done by removing old directories in the path, that older than 30 days. NFrzRemoving old version of %s: %sTr)r r!rrrrr rrrnowtimezoneutc fromtimestamprxrydaysrrr_ DAYS_TO_KEEPrHrNrr)r&rrridays_olds r7_clean_old_versionszIndex._clean_old_versions s6 L!;!;<< ##%%  F (((66 $,,.. 8 8D  00+++IIKK("+/   8)) 8L(( 2 ??? &&  9%&) "))*555'// 0BINNN-O0% j====  " " $ $ $[- %- %- %- %- %- %- %- %- %- %- %- %- %- %- %^s8=A/G -DG B FG F/G  GGc Kj}t||d{V}t |\}}|p|}|s6t djdStj j}| r| dnd}t|} t5} | jjdd| t&j| d |r|r|n|} | rAt| | |fd |Dd{V| ||d{V t3| d z jjd 5} | t7j|dddn #1swxYwY| | |}n6#t@tBf$r"} tEtG| | d} ~ wwxYw| $dddn #1swxYwY|rE|r1t d |t'j|d jtJj&j'vS)z Run update, return whether updated. :raise UpdateError: if OSError or http error or integrity check error (got wrong data from the server) ruNzupdating %s: nothing to update.FrrCrTrc3LK|]}|jVdSr,rsrts r7rTz$Index._run_update..sD$$9=**4844$$$$$$rCr^rwz,Removing old path on file by file update: %s)(rrrrrrHrN_touchr r!rrrrrrrrDrrrr _copytreeunionrrsrr{dumpsencoder9rrTr3r[rrrr_r`)r&rvras_jsonrr need_updaterrrr from_pathrwrs` r7 _run_updatezIndex._run_updates'' 22#C999999999#66vgGG 9,9   KK949 E E E KKMMM5L!;!;<< 09/C/C/E/E OI  U  + + +4 ++I66[[* %N NN$+di07%      # # xt $   %I):):I  !! ooOO$$$$AJ$$$$$Xy'$JJ J J J J J J J 1$11K *62=JJtz'2299;;<<< =============== h''' ::8YOO"G, 1 1 1!#a&&))q0 1  " " $ $ $U* %* %* %* %* %* %* %* %* %* %* %* %* %* %* %Z  8)) 8 KK>    M($ 7 7 7 7y 2 ;;;s[CK15)J:I$ J$I( (J+I( ,.JK1K,K  KK11K58K5from_dirto_dir ignored_pathsc`Kfd}ttj||d|dd{VdS)z7Copy *from_dir* to *to_dir* except for *ignored_paths*.cttjtsJt fd|DS)z(Return names that should not be copied.c3`K|](}tj|v$|V)dSr,)rmrir)rrr ris r7rTz8Index._copytree..ignore_names..sJ7<<d++}<<<<<<rC) isinstancermr?r frozenset)rinamesr s` r7 ignore_namesz%Index._copytree..ignore_namess_bioos33 3 33! rCT)symlinksignore dirs_exist_okN)r rcopytree)r r r rs ` r7rzIndex._copytreesu       O               rCrctjt|j|j}|j|j}tj|tj|j vsJd ||tj||}tj | |j|S)z.Return a local file path corresponding to URL.z$url ({}) does not fit file path ({})) rmrirelpathr_URL_PATH_PREFIXrFrr r!r r/rr)r&r url_relpath type_pathrLs r7rlzIndex.localfilepathsgoo SMM  5  K * L # #w|K'@'@'H H H H 1 8 8i H H I H H Y?? w||DOODI66 FFFrCc |d}tj|rtj|dSdS#t $r2}t t|Yd}~dSd}~wwxYw)z5Update mtime of description.json file so it is fresh.TrwN) rrmriisfileutimer3rHr_r)r&rirs r7rz Index._touchs #--T-::Dw~~d##    # # # NN3q66 " " " " " " " " " #sA A B 'BB cKt|j|jtgD]}} |||d{V#tt f$r&}t d||Yd}~Hd}~wt$r&}t d||Yd}~vd}~wwxYwt d|jd| zdS)Nzhook %s error: %sz%s files update finished%sz (not updated)) r _HOOKSrr%rTrrHrr exceptionrN)r&rhookrs r7rzIndex._run_hookss$+di0<.AA ? ?D ?d4,,,,,,,,,,"N3 ; ; ; 0$:::::::: ? ? ?  !4dA>>>>>>>> ? ( I J /     s!?B$A11 B$>BB$c$Ktjj}|sy||d{Vs^td|jttjjdz| dd{VdS|j o|j |j}| }|rd}td|j| tj |tjj|d{V}|r!td|j|nG#tjt"f$r.}td |j||d }Yd}~nd}~wwxYw|rd }td|j| tj |tjj|d{V}|r!td|j|nr#tjt"f$rY}td |j||| dd{V|j|jvr|Yd}~dSd}~wwxYw| |p|d{VdS) aRun update for the current `type` of files. Normally update is performed when either is true: * index is never been fetched (description.json missing or broken); * last update was performed longer than configured period of time ago; * some local files are missing or have wrong content (md5 hash differs from description.json). If force is True then update is performed unconditionally. Raises asyncio.TimeoutError, UpdateError. Nz(%s was updated less than %s minutes ago.<FrrzUpdating %s files via %szUpdated %s using %sz%s update error via %s: %sTzfile by file download)rr_TIMEOUTrrHrNrrr}rrrPr`wait_forrSOCKET_TIMEOUT TimeoutErrorr[r_r rO)r&rrvrJ file_by_filelog_strupdatedrs r7rz Index.update s$, 4#8#8#A#AAAAAAA  KK: F&-344    //U/33 3 3 3 3 3 3 3 F.ET%:49%E"{  $G KK2DIw G G G $ ' 0,,*9 !! KKK 5ty'JJJ(+6 $ $ $0$)Wa $  $  -G KK2DIw G G G  ' 0$$V%7%FGG!!KKK 5ty'JJJ(+6   0$)Waooo7777777779 555GFFFFF oo)9Eo:::::::::::s3A D22E6$E11E6A HI/AI**I/ only_typecK|rj||d}||4d{V||d{Vdddd{VdS#1d{VswxYwYdS|rtd|jD]i}||d}||4d{V||d{Vdddd{Vn#1d{VswxYwYjdStd|jD]i}||d}||4d{V||d{Vdddd{Vn#1d{VswxYwYjdS)zkRun update for all registered `types` of files. Raises asyncio.TimeoutError, UpdateError. Fr5NzUpdating essential fileszUpdating all files)rprrHrNrOr)r@r-ronly_essentialindexr's r7 update_allzIndex.update_allOss  .C 5999Ezz),, * * * * * * * *ll5))))))))) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *  . KK2 3 3 3- . .E5999::e,,........,,u---------........................... . . KK, - - - . .E5999::e,,........,,u---------........................... . .s5A A&)A&<C** C4 7C4 E77 F F cF|j||dS)z:Add a hook for type_ to be called after successful update.N)r!rN)r@r'r#s r7add_hookzIndex.add_hookhs% 5d#####rC)T)Fr&N)NFF)OrVrWrXrr`Lockrorkr!rFrDrrOrPrrrrHrrr*r/r3rmPathLiker9r6rrArrrrQrWrrZrrrar$rprrrrfloatr{rrrr r!rrrrrr staticmethodrrrrrr rgrrrrrr rrlrrrr1r3rBrCr7rr$s K % %E [  F F F SUUFsuu 6::QX666v|DD)#)#)#)#V       32; 34 3 3 3 3 2;       ..... .  ... ...[.8  D    [  !c#h!!![!:s:s:::[:LLSLLLLc #c(      [ Mx}MMMM###.4EdEEEE  e       ----!,36u: 2$J$ s5z3s8# $$$$$.KKKKK[KBBBBB[B : : :\ :@gl@w|@@@\@O >?%%%%N$s) 88884C C18C ', CCCCJNr"rr_MAX_TRIES_FOR_DOWNLOADrdinfrrrr7JSONTyper=__annotations__r>rDrJrOrR RuntimeErrorrTr[rhr6rsrrrrrrrzrrrrrrrrr?r1rrWrArCrBrCr7r^s  ////////::::::::::99999999%%%%%%                      "!!!!!-,,,,,888888======EEEEEEEEEE99999999333333NMMMMM 8  #w|$DEEA: $4 %   GL0 1 1 1  ( c5$d38nd3iG H8< X3 4;;;%) H\ ")))GdGGGGMMMM 6     PPPPP\PPP     ,   KKK"+SX h     ! !) ! ! ! ! ! ,8O"=3"=H"="="="=L*, ! ! !  ! ! ! ! !  ,8O7 7"7-27 7777t59) ) ) C) U) ) ) ) X&&&&&R,I,#,$,,,,  %5  0L0L0L 0L{0L  0L 0L0L0L  0Lf  7UH-..JJUJJJJ ;;;F@Il07  ,G $G $G $G $G $G $G $G $TOOOO,  3,1   }             rCdefence360agent/files/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000000251100000000000020643 0ustar r_joHdZddlmZddlmZmZeeZiZddZ dS)z9Run default hooks for files update and log errors if any.) getLogger) check_run CheckRunErrorreturnNcK|rt|j}|rb|rP t |gd{VdS#t $r&}t d|Yd}~dSd}~wwxYwdSdSdS)zDRun delivered hooks for files update. Errors are logged up on stack.NzError during hook execution: %s) DEFAULT_HOOKSgettypeexistsrrloggererror)files_index_object is_updatedhookes P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/hooks.py default_hookr sC  !3!899  CDKKMM C C'''''''''''  C C C >BBBBBBBBB C CC C C C CsA BA>>B)rN) __doc__loggingrdefence360agent.utilsrr__name__r rrrrsm??:::::::: 8   CCCCCCrdefence360agent/files/__pycache__/hooks.cpython-311.pyc0000644000000000000000000000251100000000000017704 0ustar r_joHdZddlmZddlmZmZeeZiZddZ dS)z9Run default hooks for files update and log errors if any.) getLogger) check_run CheckRunErrorreturnNcK|rt|j}|rb|rP t |gd{VdS#t $r&}t d|Yd}~dSd}~wwxYwdSdSdS)zDRun delivered hooks for files update. Errors are logged up on stack.NzError during hook execution: %s) DEFAULT_HOOKSgettypeexistsrrloggererror)files_index_object is_updatedhookes P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/files/hooks.py default_hookr sC  !3!899  CDKKMM C C'''''''''''  C C C >BBBBBBBBB C CC C C C CsA BA>>B)rN) __doc__loggingrdefence360agent.utilsrr__name__r rrrrsm??:::::::: 8   CCCCCCrdefence360agent/files/hooks.py0000644000000000000000000000115700000000000013351 0ustar """Run default hooks for files update and log errors if any.""" from logging import getLogger from defence360agent.utils import check_run, CheckRunError logger = getLogger(__name__) DEFAULT_HOOKS = {} async def default_hook(files_index_object, is_updated) -> None: """Run delivered hooks for files update. Errors are logged up on stack.""" if is_updated: hook = DEFAULT_HOOKS.get(files_index_object.type) if hook and hook.exists(): try: await check_run([hook]) except CheckRunError as e: logger.error("Error during hook execution: %s", e) defence360agent/hooks/0000755000000000000000000000000000000000000011671 5ustar defence360agent/hooks/__init__.py0000644000000000000000000000000000000000000013770 0ustar defence360agent/hooks/__pycache__/0000755000000000000000000000000000000000000014101 5ustar defence360agent/hooks/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030000000000000021272 0ustar r_jdS)NrS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/__init__.pyrsrdefence360agent/hooks/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030000000000000020333 0ustar r_jdS)NrS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/__init__.pyrsrdefence360agent/hooks/__pycache__/execute.cpython-311.opt-1.pyc0000644000000000000000000001753700000000000021221 0ustar r_jAddlZddlZddlZddlZddlZddlmZddlmZ ddl m Z ddl m Z ddlmZddlmZmZe ZdZd d Zd d Zejfd ZdS)N)Corenative)EventHookLogger) EventHook)db)run snake_casectjrgStjtj|k}t |S)N)rdeferredrselectwhereeventlist)rhookss R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/execute.py get_hooksrsB {     $ $Y_%= > >E ;;FcXtj|s"td||rBtj|tjs"td|nAtj|tjs"td|tj|} tj |}n5#t$r(}td||d}~wwxYw|j tj zr"td|tj |}|r|dkr tj |}n5#t$r(}td||d}~wwxYw|j tj zr;|j tjzs)td ||dSdSdSdS) a'Raise ValueError if path is not a safe hook file. The original check rejected any path under /tmp, /var/tmp, /dev/shm on the grounds that those dirs are world-writable. That blanket- by-prefix rule was too coarse: pytest's tmp_path lives under /tmp/pytest-of-/... and the agent's own integration fixtures legitimately put hook files there. The real threats are (a) an attacker-owned file (DB row points at a path the attacker controls) and (b) a hook whose immediate parent is world-writable so the file can be swapped between this check and the exec. The required permission bit differs between branches: subprocess hooks are exec'd by the kernel (needs X_OK), but native hooks are loaded via importlib's open()+exec_module path which only needs R_OK. A standard Python file in mode 0o644 is loadable but not executable, so requiring +x for native hooks would silently break the typical native-hook deployment (the `hook add-native` RPC has never required or documented an executable bit). z-Hook path does not exist or is not a file: {}zHook path is not readable: {}zHook path is not executable: {}zHook path stat failed: {}: {}NzHook path is world-writable: {}/zHook parent stat failed: {}: {}z=Hook path has world-writable parent without sticky bit {}: {})ospathisfile ValueErrorformataccessR_OKX_OKrealpathstatOSErrorst_modeS_IWOTHdirnameS_ISVTX)rrrealstexcparentpsts r_validate_hook_pathr+s;( 7>>$    ; B B4 H H   Myrw'' K<CCDIIJJ J Kyrw'' M>EEdKKLL L 7  D ! !DL WT]] LLL8??cJJKKKL zDL I:AA$GGHHH W__T " "F &C-- '&//CC   188EE   K$, & t|1K  &.. --    s0'C<< D.#D))D.F$$ G.#GGc^K tj}|dt||d{V|rt j||dSt j|}tj |} t|gd||d{V\}}}nK#t$r}dt|fcYd}~Sd}~wt$r}dt|fcYd}~Sd}~wwxYw||fS#t $r} dt| fcYd} ~ Sd} ~ wwxYw)N)rNF)shellinputcwd~)asyncioget_event_looprun_in_executorr+ native_hooks execute_hookjsondumpsencoderrr$r FileNotFoundErrorreprPermissionError Exception) rdatarloopr/ exit_code_errr(es rr6r6Ts%''""4) ! ! ! ! ! ! " " "S > ! ! ! ! ! ! "#~ T!WW}smA DADB87D8 DCDD D%C;5D6D;DD D,D'!D,'D,c.K|d}t|}t|j}|sdSt |j|j5}|rt |jjdz}tj d|d|}tj ||| tj||j|d<|j|j|d}|D]} || j| j5} | t+| j|| jd{V\} } | | | dddn #1swxYwY ddddS#1swxYwYdS) NDUMPrAzw+z.json)modeprefixsuffixdir tmp_filename)rsubtypeparamsr)getdictrrevent_hook_loggerrKr __class____name__tempfileNamedTemporaryFiler7dumpflushrfsyncfilenonamerrbeginr6finish) rtempdirrTrLr event_loggerrGtmpr>hook hook_loggerr@rBs r execute_hooksr`rs? 99V  D %[[F ek " "E  5; 6 63,  . 899C?F-&gC IdC IIKKK HSZZ\\ " " "%(XF> "[}    3 3Ddi <<< 3 !!###'3ItDK((("""""" 3""9c222  3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3#333333333333333333s8B;F AE0$ F 0E4 4F 7E4 8F  FF)F)r2r7rr rR defence360agent.contracts.configrdefence360agent.hooksrr5 defence360agent.internals.loggerr defence360agent.model.event_hookrdefence360agent.model.instancerdefence360agent.utilsr r rOrr+r6TMPDIRr`rrris 111111888888<<<<<<666666------11111111#O%%7777t<(,{333333rdefence360agent/hooks/__pycache__/execute.cpython-311.pyc0000644000000000000000000001753700000000000020262 0ustar r_jAddlZddlZddlZddlZddlZddlmZddlmZ ddl m Z ddl m Z ddlmZddlmZmZe ZdZd d Zd d Zejfd ZdS)N)Corenative)EventHookLogger) EventHook)db)run snake_casectjrgStjtj|k}t |S)N)rdeferredrselectwhereeventlist)rhookss R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/execute.py get_hooksrsB {     $ $Y_%= > >E ;;FcXtj|s"td||rBtj|tjs"td|nAtj|tjs"td|tj|} tj |}n5#t$r(}td||d}~wwxYw|j tj zr"td|tj |}|r|dkr tj |}n5#t$r(}td||d}~wwxYw|j tj zr;|j tjzs)td ||dSdSdSdS) a'Raise ValueError if path is not a safe hook file. The original check rejected any path under /tmp, /var/tmp, /dev/shm on the grounds that those dirs are world-writable. That blanket- by-prefix rule was too coarse: pytest's tmp_path lives under /tmp/pytest-of-/... and the agent's own integration fixtures legitimately put hook files there. The real threats are (a) an attacker-owned file (DB row points at a path the attacker controls) and (b) a hook whose immediate parent is world-writable so the file can be swapped between this check and the exec. The required permission bit differs between branches: subprocess hooks are exec'd by the kernel (needs X_OK), but native hooks are loaded via importlib's open()+exec_module path which only needs R_OK. A standard Python file in mode 0o644 is loadable but not executable, so requiring +x for native hooks would silently break the typical native-hook deployment (the `hook add-native` RPC has never required or documented an executable bit). z-Hook path does not exist or is not a file: {}zHook path is not readable: {}zHook path is not executable: {}zHook path stat failed: {}: {}NzHook path is world-writable: {}/zHook parent stat failed: {}: {}z=Hook path has world-writable parent without sticky bit {}: {})ospathisfile ValueErrorformataccessR_OKX_OKrealpathstatOSErrorst_modeS_IWOTHdirnameS_ISVTX)rrrealstexcparentpsts r_validate_hook_pathr+s;( 7>>$    ; B B4 H H   Myrw'' K<CCDIIJJ J Kyrw'' M>EEdKKLL L 7  D ! !DL WT]] LLL8??cJJKKKL zDL I:AA$GGHHH W__T " "F &C-- '&//CC   188EE   K$, & t|1K  &.. --    s0'C<< D.#D))D.F$$ G.#GGc^K tj}|dt||d{V|rt j||dSt j|}tj |} t|gd||d{V\}}}nK#t$r}dt|fcYd}~Sd}~wt$r}dt|fcYd}~Sd}~wwxYw||fS#t $r} dt| fcYd} ~ Sd} ~ wwxYw)N)rNF)shellinputcwd~)asyncioget_event_looprun_in_executorr+ native_hooks execute_hookjsondumpsencoderrr$r FileNotFoundErrorreprPermissionError Exception) rdatarloopr/ exit_code_errr(es rr6r6Ts%''""4) ! ! ! ! ! ! " " "S > ! ! ! ! ! ! "#~ T!WW}smA DADB87D8 DCDD D%C;5D6D;DD D,D'!D,'D,c.K|d}t|}t|j}|sdSt |j|j5}|rt |jjdz}tj d|d|}tj ||| tj||j|d<|j|j|d}|D]} || j| j5} | t+| j|| jd{V\} } | | | dddn #1swxYwY ddddS#1swxYwYdS) NDUMPrAzw+z.json)modeprefixsuffixdir tmp_filename)rsubtypeparamsr)getdictrrevent_hook_loggerrKr __class____name__tempfileNamedTemporaryFiler7dumpflushrfsyncfilenonamerrbeginr6finish) rtempdirrTrLr event_loggerrGtmpr>hook hook_loggerr@rBs r execute_hooksr`rs? 99V  D %[[F ek " "E  5; 6 63,  . 899C?F-&gC IdC IIKKK HSZZ\\ " " "%(XF> "[}    3 3Ddi <<< 3 !!###'3ItDK((("""""" 3""9c222  3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3#333333333333333333s8B;F AE0$ F 0E4 4F 7E4 8F  FF)F)r2r7rr rR defence360agent.contracts.configrdefence360agent.hooksrr5 defence360agent.internals.loggerr defence360agent.model.event_hookrdefence360agent.model.instancerdefence360agent.utilsr r rOrr+r6TMPDIRr`rrris 111111888888<<<<<<666666------11111111#O%%7777t<(,{333333rdefence360agent/hooks/__pycache__/native.cpython-311.opt-1.pyc0000644000000000000000000000372600000000000021040 0ustar r_jpUddlZddlZddlmZdZeddZiZee efe d<dZ dZ d Z dS) N) namedtupleim_hook ModuleInfoobjectmtimectimemodulesctj|}|tvrHt|j|jkr-t|j|jkrt|jStj ||}tj |}|j |t||j|jt|<|S)Nr)osstatr rst_mtimer st_ctimer importlibutilspec_from_file_locationmodule_from_specloader exec_moduler)path file_statspec hook_modules Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/native.py import_hookr s I  DM 9#5 5 5 DM 9#5 5 5t}## > 1 1$ = =D.11$77KKK((()"4Ir-s """""" Z &B C C !#c:o ###$ """""rdefence360agent/hooks/__pycache__/native.cpython-311.pyc0000644000000000000000000000372600000000000020101 0ustar r_jpUddlZddlZddlmZdZeddZiZee efe d<dZ dZ d Z dS) N) namedtupleim_hook ModuleInfoobjectmtimectimemodulesctj|}|tvrHt|j|jkr-t|j|jkrt|jStj ||}tj |}|j |t||j|jt|<|S)Nr)osstatr rst_mtimer st_ctimer importlibutilspec_from_file_locationmodule_from_specloader exec_moduler)path file_statspec hook_modules Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/hooks/native.py import_hookr s I  DM 9#5 5 5 DM 9#5 5 5t}## > 1 1$ = =D.11$77KKK((()"4Ir-s """""" Z &B C C !#c:o ###$ """""rdefence360agent/hooks/execute.py0000644000000000000000000001310100000000000013701 0ustar import asyncio import json import os import stat import tempfile from defence360agent.contracts.config import Core from defence360agent.hooks import native as native_hooks from defence360agent.internals.logger import EventHookLogger from defence360agent.model.event_hook import EventHook from defence360agent.model.instance import db from defence360agent.utils import run, snake_case event_hook_logger = EventHookLogger() def get_hooks(event): # if database is not available (i.e. direct RPC call), do not try to # load hooks if db.deferred: return [] hooks = EventHook.select().where(EventHook.event == event) return list(hooks) def _validate_hook_path(path, native=False): """Raise ValueError if path is not a safe hook file. The original check rejected any path under /tmp, /var/tmp, /dev/shm on the grounds that those dirs are world-writable. That blanket- by-prefix rule was too coarse: pytest's tmp_path lives under /tmp/pytest-of-/... and the agent's own integration fixtures legitimately put hook files there. The real threats are (a) an attacker-owned file (DB row points at a path the attacker controls) and (b) a hook whose immediate parent is world-writable so the file can be swapped between this check and the exec. The required permission bit differs between branches: subprocess hooks are exec'd by the kernel (needs X_OK), but native hooks are loaded via importlib's open()+exec_module path which only needs R_OK. A standard Python file in mode 0o644 is loadable but not executable, so requiring +x for native hooks would silently break the typical native-hook deployment (the `hook add-native` RPC has never required or documented an executable bit). """ if not os.path.isfile(path): raise ValueError( "Hook path does not exist or is not a file: {}".format(path) ) if native: if not os.access(path, os.R_OK): raise ValueError("Hook path is not readable: {}".format(path)) else: if not os.access(path, os.X_OK): raise ValueError("Hook path is not executable: {}".format(path)) real = os.path.realpath(path) try: st = os.stat(real) except OSError as exc: raise ValueError("Hook path stat failed: {}: {}".format(path, exc)) # Reject world-writable files: any unprivileged user could rewrite # them between this check and the subprocess/importlib load. if st.st_mode & stat.S_IWOTH: raise ValueError("Hook path is world-writable: {}".format(path)) parent = os.path.dirname(real) if parent and parent != "/": try: pst = os.stat(parent) except OSError as exc: raise ValueError( "Hook parent stat failed: {}: {}".format(parent, exc) ) # A world-writable parent without the sticky bit means an # attacker can replace our hook by deleting+recreating the # file. /tmp itself has the sticky bit so renames are owner- # only, which is safe; pytest's tmp_path subdirs are mode 700. if (pst.st_mode & stat.S_IWOTH) and not (pst.st_mode & stat.S_ISVTX): raise ValueError( "Hook path has world-writable parent without sticky bit" " {}: {}".format(parent, path) ) async def execute_hook(path, data, native=False): try: # Path validation runs filesystem syscalls (isfile/access/realpath) # which can block the event loop on slow/NFS storage; defer to a # threadpool executor. The same checks apply to native hooks # because native_hooks.execute_hook imports the file via importlib # straight in the agent's root process — a DB-sourced /tmp path # there is at least as dangerous as a subprocess fork. loop = asyncio.get_event_loop() await loop.run_in_executor(None, _validate_hook_path, path, native) if native: native_hooks.execute_hook(path, data) return 0, None data = json.dumps(data).encode() cwd = os.path.dirname(path) try: exit_code, _, err = await run( [path], shell=False, input=data, cwd=cwd ) except FileNotFoundError as exc: # 127 = shell convention for "command not found". return 127, repr(exc) except PermissionError as exc: # 126 = shell convention for "found but not executable". return 126, repr(exc) return exit_code, err except Exception as e: return None, repr(e) async def execute_hooks(event, tempdir=Core.TMPDIR): dump = event.get("DUMP") params = dict(event) hooks = get_hooks(event.event) if not hooks: return with event_hook_logger(event.event, event.subtype) as event_logger: if dump: prefix = snake_case(event.__class__.__name__) + "_" tmp = tempfile.NamedTemporaryFile( mode="w+", prefix=prefix, suffix=".json", dir=tempdir ) json.dump(dump, tmp) tmp.flush() os.fsync(tmp.fileno()) params["tmp_filename"] = tmp.name data = { "event": event.event, "subtype": event.subtype, "params": params, } for hook in hooks: with event_logger(hook.path, native=hook.native) as hook_logger: hook_logger.begin() exit_code, err = await execute_hook( hook.path, data, native=hook.native ) hook_logger.finish(exit_code, err) defence360agent/hooks/native.py0000644000000000000000000000171000000000000013530 0ustar import importlib.util import os from collections import namedtuple ENTRYPOINT = "im_hook" ModuleInfo = namedtuple("ModuleInfo", ("object", "mtime", "ctime")) modules: dict[str, ModuleInfo] = {} def import_hook(path): file_stat = os.stat(path) if ( path in modules and modules[path].mtime == file_stat.st_mtime and modules[path].ctime == file_stat.st_ctime ): return modules[path].object spec = importlib.util.spec_from_file_location(path, path) hook_module = importlib.util.module_from_spec(spec) spec.loader.exec_module(hook_module) modules[path] = ModuleInfo( object=hook_module, mtime=file_stat.st_mtime, ctime=file_stat.st_ctime ) return hook_module def remove_hook(path): if path in modules: modules.pop(path) def execute_hook(path, dict_param): hook_module = import_hook(path) entrypoint = getattr(hook_module, ENTRYPOINT) return entrypoint(dict_param) defence360agent/internals/0000755000000000000000000000000000000000000012545 5ustar defence360agent/internals/__init__.py0000644000000000000000000000000000000000000014644 0ustar defence360agent/internals/__pycache__/0000755000000000000000000000000000000000000014755 5ustar defence360agent/internals/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030400000000000022152 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/__init__.pyrsrdefence360agent/internals/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021213 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/__init__.pyrsrdefence360agent/internals/__pycache__/auth_protocol.cpython-311.opt-1.pyc0000644000000000000000000000342000000000000023277 0ustar r_jlddlZddlZddlZddlZejeZGddejZdS)NceZdZdZdZdZdS)UnixSocketAuthProtocolz This protocol uses SO_PEERCRED attribute of unix socket to get authentication data (pid, uid, gid) After connect, this values are stored in object's _pid, _uid, _gid attributes 3ic~||_|jd}|tjtjt j|j}t j |j|\|_ |_ |_ td|j |j |j dS)Nsocketz1New socket connection from pid=%s, uid=%s, gid=%s) _transportget_extra_info getsockoptr SOL_SOCKET SO_PEERCREDstructcalcsize STRUCT_FORMATunpack_pid_uid_gidloggerdebug)self transportconncredss \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/auth_protocol.pyconnection_madez&UnixSocketAuthProtocol.connection_mades#--h77     OD. / /   +1-  + + ' 49di  ? I I I      N)__name__ __module__ __qualname____doc__rrrrrr s4M     rr) asynciorloggingr getLoggerrrProtocolrr!rrr&sr   8 $ $" " " " " W-" " " " " rdefence360agent/internals/__pycache__/auth_protocol.cpython-311.pyc0000644000000000000000000000342000000000000022340 0ustar r_jlddlZddlZddlZddlZejeZGddejZdS)NceZdZdZdZdZdS)UnixSocketAuthProtocolz This protocol uses SO_PEERCRED attribute of unix socket to get authentication data (pid, uid, gid) After connect, this values are stored in object's _pid, _uid, _gid attributes 3ic~||_|jd}|tjtjt j|j}t j |j|\|_ |_ |_ td|j |j |j dS)Nsocketz1New socket connection from pid=%s, uid=%s, gid=%s) _transportget_extra_info getsockoptr SOL_SOCKET SO_PEERCREDstructcalcsize STRUCT_FORMATunpack_pid_uid_gidloggerdebug)self transportconncredss \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/auth_protocol.pyconnection_madez&UnixSocketAuthProtocol.connection_mades#--h77     OD. / /   +1-  + + ' 49di  ? I I I      N)__name__ __module__ __qualname____doc__rrrrrr s4M     rr) asynciorloggingr getLoggerrrProtocolrr!rrr&sr   8 $ $" " " " " W-" " " " " rdefence360agent/internals/__pycache__/cln.cpython-311.opt-1.pyc0000644000000000000000000005177400000000000021210 0ustar r_j6ddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddlm Z mZmZmZmZddlZddlmZddlmZddlmZddlmZmZmZdd lmZd Ze d Z ej!e"Z#d Z$ed de%fdZ&dZ'Gdde(Z)Gdde(Z*Gdde)Z+ddZ,GddZ-GddZ.dZ/dS)N) defaultdict)Path) parse_qsl urlencodeurljoinurlparse urlunparse)ANTIVIRUS_MODE) LicenseCLN) HostingPanel) CheckRunErrorasync_lru_cache check_run) get_hostnamei,z/usr/sbin/ie-configzpwget -qq -O - https://repo.imunify360.cloudlinux.com/defence360/imunifyemail-deploy.sh | bash -s 'is-supported')maxsizereturncK ttdd{VnM#t$r@}|jdkr*tdt |Yd}~dSd}~wwxYwdS)NT)shelldzimunify-email check failed F)rIE_SUPPORTED_CMDr returncodeloggererrorstr)es R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/cln.pyis_imunify_email_supportedrs(55555555555  <3   LL?s1vv?? @ @ @uuuuu 4s! A+5A&&A+cKtrdStsdS tt tdgd{V}n#t $rYdSwxYwd|vS)zTry to get imunify-email statusFstatusNz&spamfilter exim configuration: enabled)r _IMUNIFY_EMAIL_CONFIG_EXECUTABLEexistsrrr decode)outputs rget_imunify_email_statusr%*su + 2 2 4 4u 1 2 2H =         uu 3v}} FFs)A A A ceZdZddZdZdS)CLNErrorNc"||_||_dSNmessager )selfr r+s r__init__zCLNError.__init__:s  cR|jr|jSd|jS)Nz#Unexpected status code from CLN: {})r+formatr r,s r__str__zCLNError.__str__>s* < < 4;;DKHHHr.)NN)__name__ __module__ __qualname__r-r2r.rr'r'9s<IIIIIr.r'ceZdZdS)InvalidLicenseErrorN)r3r4r5r6r.rr8r8EsDr.r8c*eZdZdZdZdZdZdZdS)BackupNotFoundi@c||_dSr)url)r,r=s rr-zBackupNotFound.__init__Ls r.cdS)NzBackup not found in CLNr6r1s rr2zBackupNotFound.__str__Os((r.c "|jdSt|j}tt|j}||d<t |j|j|j |j t||j fS)N used_space) r=rdictrquery _disk_usager schemenetlocpathparamsrfragment)r,purBs radd_used_spacezBackupNotFound.add_used_spaceRs 8  F dh  Yrx(())"..00l   %       r.cNd}tj}t}|D]i}|j|vr^d|jvrU|jds;|tj|jjz }| |jjt||j z S)Nrnoautoz /dev/loop) psutildisk_partitionssetdeviceopts startswith disk_usage mountpointusedaddroundGB)r, total_used partitions processedps rrCzBackupNotFound._disk_usagees +-- EE  ( (A**QV++,,[99,f/ ==BB  ah'''Z$')***r.N)r3r4r5rXr-r2rJrCr6r.rr:r:IsU B)))   & + + + + +r.r:c i}|||d<|t|tr|dddint|tr.|d}|dddinJt j|d}|dddi||d < t j t jj |fi|| }|5|j d kr|j dfcdddS|j d vr | } |j tj|fcdddS#tj$r'}t#d |d|j |j |d}~wwxYw#t$j$rt)dwxYwt#|j #1swxYwYdS#t jj$r}|j dkrt#|j |d}|jwt0d||||j |j | } n"#t$j$rt)dwxYw| d}t#||j |d}~wt jj$r#}t#t||d}~wt$j$rt)dt8$r%}t0d|||||d}~wwxYw)z!To be used by RestCLN._request().Nheaders Content-typezapplication/octet-streamzutf-8ztext/plain; charset=utf-8asciiz!application/x-www-form-urlencodeddata)timeout)zNon-json data from CLN: z for code=r*zTimed out reading responseiz,CLN.post(url=%r, data=%r, headers=%r): %d %szTimed out reading error messagereplace)errors)r+zTimed out receiving responsez5CLN.post(url=%r, data=%r, headers=%r, timeout=%r): %s) isinstancebytes setdefaultrencodeurllibparserrequesturlopenRequestcodereadjsonloadsr#JSONDecodeErrorr'socketrb TimeoutErrorr HTTPErrorfprwarningreasonURLErrorOSError) r=rar^rbkwargsrespcontentrr+ resp_datas r _post_requestrtsX F#y  dE " "    N,FG    c " " ;;w''D   N,GH    <))$//66w??D   !DE   v?*~%% N "3 1 1& 1 17&  R * *yCy$ * * * * * * * *j((!"iikkG !#y$*W^^5E5E*F*FF * * * * * * * * /!!!&!57!5!5)-!5!5$(9  ! !! ~EEE&'CDDDEty)))+ * * * * * * * * * *M < !>>> 6C<<16"" ) 4  NN>     FFFHH > F F F"#DEEE F &&i&88Gwqv666A= < ...s1vv&&&A- >;;;9:::     C          s 6G:G-& G-0F7,E>>F4 "F//F44G-7GG--G14G1:M AK!I65K6J0KM K::*M $ MM cjeZdZdZdZejddZejddZ e ejdeZ e e dZ e e dZe e d Ze e d Ze e d Ze e d Zd ZdZeddeddZedZededefdZe ddededefdZededefdZedefdZededefdZ eddZ!dS)RestCLNzhttps://{domain}/api/im/zcln.cloudlinux.comIM360_CLN_API_BASE_URLzipv6.cln.cloudlinux.comzipv4.cln.cloudlinux.comdomainregister unregistercheckinzab/credentialsz ab/removezab/checkokzok-trialN)rar^rbcrKtjdt||||d{VSr))asyncioget_event_looprun_in_executorr)clsr=rar^rbs r_requestzRestCLN._requestsR+--== -dGW         r.cKt|j|jd}dt d} |||d{V\}}ns#t $rf}|jdkrOt|j|jd}|||d{V\}}n|Yd}~nd}~wwxYw|S)NrrIPLkeyhostnamerai) r_URL_PATH_TEMPLATEr0_IPV4_DOMAIN_NAMErrr'r _IPV6_DOMAIN_NAME)rv4_license_urlra_token cln_errorv6_license_urls rprocess_ipl_licencezRestCLN.process_ipl_licences!  " ) )1F ) G G   ,..99  \\.t\DDDDDDDDHAuu 3&&!(*11"42 """%n4!H!HHHHHHH555555  s A$$ C.ACCrrcK|dkr|d{VS||j|tdd{V\}}|S)z Register server with key :param key: registration key :return: license token in case of success rNrr)rr _REGISTER_URLr)rrrrs rrzRestCLN.registers %<<0022222222 2  ,..99&        5 r. server_id users_countrc"K|p t}td{V}t} |d{V}nH#t$r;}t dt|d|j}Yd}~nd}~wwxYw|||||dtd{Vidd}tj |} t d| | |j| d d i d{V\} } | S) z Update license token :param str server_id: server id :param int users_count: users count :param str hostname: current server hostname :return: dict new license token NzFailed to get panel version: %sT)exc_infoIM_EMAIL)userspanel imunifyEmailsupported_features)idrimzCLN checkin: %sr_zapplication/json)rar^)rr%r name ExceptionrrrNAMErrsdumpsinfor _CHECKIN_URL) rrrrimunify_email_statusr panel_namerreqrarrs rrzRestCLN.checkins~-|~~%=%?%??????? $$zz||++++++JJ $ $ $ LL13q66D    JJJJJJ  $ $# 4&@&B&B B B B B B B'    z# %t,,,  #%78&        5  sA B1BBcXK||jd|id{V\}}|S)zl Creates Acronis Backup account and get user & password :param server_id: server id rrN)r_ACRONIS_CREDENTIALS_URL)rrrcredss racronis_credentialszRestCLN.acronis_credentials<sV   (i/@&        5 r.cRK||jd|id{VdS)zT Removes Acronis Backup account :param server_id: server id rrN)r_ACRONIS_REMOVE_URLrrs racronis_removezRestCLN.acronis_removeGs> ll32$ 9JlKKKKKKKKKKKr.cK||jd|id{V\}}|dkrtd|S)z If Acronis account exists return backup size in GB or if backups not exists URL for backups :param server_id: server id rrNrer<)r_ACRONIS_CHECK_URLr:)rrr responses r acronis_checkzRestCLN.acronis_checkOsq"%  "$ ):"." "        S== T*** *r.c|K|ptj}||jd|id{VdS)z< Unregister server id :return: None rrN)r get_server_idr_UNREGISTER_URLrs rrzRestCLN.unregister]sQ ;!9!;!; ll3.dI5FlGGGGGGGGGGGr.r))"r3r4r5r_BASE_DOMAIN_NAMEosenvirongetrrr0 _BASE_URLrrrrrrrSTATUS_OK_PAID_LICENSESTATUS_OK_TRIAL_LICENSE classmethod_TIMEOUTrrrrArintrrrrrr6r.rrrsk3,  ";  ";#))z~~68IJJ*IGIz22Mgi66O79i00L&wy2BCC!')[99 J77!()-tX    [ [,     [   ++++ +++[+Z#$[LSLLL[L C D   [ HHH[HHHr.rceZdZeeZedZedZedZ edZ edZ edZ dS)CLNcF|j||dSr)) _CALLBACKSrV)r method_name coro_callbacks radd_callback_forzCLN.add_callback_forjs# {#'' 66666r.c K|j|D]h} |d{V#tj$rt$r9}td|||Yd}~ad}~wwxYwdS)Nz;Error '{!r}' happened when run callback {} forCLN {} method)rrCancelledErrorrr exceptionr0)rrcallbackrs rrun_callbacks_forzCLN.run_callbacks_forns{3  H hjj        )        $$*F1h $D$D   s$A6/A11A6c,|dS)NIMAVP)rR)rrs r is_avp_keyzCLN.is_avp_key{s~~g&&&r.cK||rtstdt|d{V}t j|s5t|dd{Vtdt j|| dd{VdS)Nz4Imunify360 can not be registered with ImunifyAV+ keyrz"License is invalid for this serverr) rr r8rrr is_validrupdater)rrlicenses rrz CLN.registers >>#   ~ %F  ((--------"7++ L$$WT]33 3 3 3 3 3 3 3%&JKK K'"""##J///////////r.cKtd{Vtj|dd{VdS)Nr)rrr deleter)rs rrzCLN.unregisterse  """""""""##L11111111111r.c$KtjrtjStjdrtjSt|dtjd{V}td|| t d{Vntj || dd{VtjS)z>Refreshes token and returns new one on success, None otherwiseis_alternativerNzGot new token from CLN: %s refresh_token) r is_free get_tokenrrrrrrrrrr)rr new_tokens rrzCLN.refresh_tokens      *')) )   ! ! % %&6 7 7 *')) )!//%+z7MNNNNNNNN  0)<<<  .."" " " " " " " " "  i ( ( (##O444444444#%%%r.N) r3r4r5rrOrrrrrrrrr6r.rrrgsS!!J77[7  [ ''[' 0 0[ 022[2 &&[&&&r.rcHdD]}t||dS)N)rrr)r)rr)corors rsubscribe_to_license_changesrs7B>>  [====>>r.)NNN)0rrsloggingrrv urllib.errorrl urllib.parseurllib.request collectionsrpathlibrrrrrr rM defence360agent.contracts.configr !defence360agent.contracts.licenser +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr rrdefence360agent.utils.commonrrr! getLoggerr3rrboolrr%rr'r8r:rrrrr6r.rrs  ######LLLLLLLLLLLLLL ;;;;;;888888DDDDDDKKKKKKKKKK555555 #'4(=#>#>  8 $ $ $ G G G I I I I Iy I I I     )   (+(+(+(+(+X(+(+(+VU*U*U*U*pXHXHXHXHXHXHXHXHv@&@&@&@&@&@&@&@&F>>>>>r.defence360agent/internals/__pycache__/cln.cpython-311.pyc0000644000000000000000000005177400000000000020251 0ustar r_j6ddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddlm Z mZmZmZmZddlZddlmZddlmZddlmZddlmZmZmZdd lmZd Ze d Z ej!e"Z#d Z$ed de%fdZ&dZ'Gdde(Z)Gdde(Z*Gdde)Z+ddZ,GddZ-GddZ.dZ/dS)N) defaultdict)Path) parse_qsl urlencodeurljoinurlparse urlunparse)ANTIVIRUS_MODE) LicenseCLN) HostingPanel) CheckRunErrorasync_lru_cache check_run) get_hostnamei,z/usr/sbin/ie-configzpwget -qq -O - https://repo.imunify360.cloudlinux.com/defence360/imunifyemail-deploy.sh | bash -s 'is-supported')maxsizereturncK ttdd{VnM#t$r@}|jdkr*tdt |Yd}~dSd}~wwxYwdS)NT)shelldzimunify-email check failed F)rIE_SUPPORTED_CMDr returncodeloggererrorstr)es R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/cln.pyis_imunify_email_supportedrs(55555555555  <3   LL?s1vv?? @ @ @uuuuu 4s! A+5A&&A+cKtrdStsdS tt tdgd{V}n#t $rYdSwxYwd|vS)zTry to get imunify-email statusFstatusNz&spamfilter exim configuration: enabled)r _IMUNIFY_EMAIL_CONFIG_EXECUTABLEexistsrrr decode)outputs rget_imunify_email_statusr%*su + 2 2 4 4u 1 2 2H =         uu 3v}} FFs)A A A ceZdZddZdZdS)CLNErrorNc"||_||_dSNmessager )selfr r+s r__init__zCLNError.__init__:s  cR|jr|jSd|jS)Nz#Unexpected status code from CLN: {})r+formatr r,s r__str__zCLNError.__str__>s* < < 4;;DKHHHr.)NN)__name__ __module__ __qualname__r-r2r.rr'r'9s<IIIIIr.r'ceZdZdS)InvalidLicenseErrorN)r3r4r5r6r.rr8r8EsDr.r8c*eZdZdZdZdZdZdZdS)BackupNotFoundi@c||_dSr)url)r,r=s rr-zBackupNotFound.__init__Ls r.cdS)NzBackup not found in CLNr6r1s rr2zBackupNotFound.__str__Os((r.c "|jdSt|j}tt|j}||d<t |j|j|j |j t||j fS)N used_space) r=rdictrquery _disk_usager schemenetlocpathparamsrfragment)r,purBs radd_used_spacezBackupNotFound.add_used_spaceRs 8  F dh  Yrx(())"..00l   %       r.cNd}tj}t}|D]i}|j|vr^d|jvrU|jds;|tj|jjz }| |jjt||j z S)Nrnoautoz /dev/loop) psutildisk_partitionssetdeviceopts startswith disk_usage mountpointusedaddroundGB)r, total_used partitions processedps rrCzBackupNotFound._disk_usagees +-- EE  ( (A**QV++,,[99,f/ ==BB  ah'''Z$')***r.N)r3r4r5rXr-r2rJrCr6r.rr:r:IsU B)))   & + + + + +r.r:c i}|||d<|t|tr|dddint|tr.|d}|dddinJt j|d}|dddi||d < t j t jj |fi|| }|5|j d kr|j dfcdddS|j d vr | } |j tj|fcdddS#tj$r'}t#d |d|j |j |d}~wwxYw#t$j$rt)dwxYwt#|j #1swxYwYdS#t jj$r}|j dkrt#|j |d}|jwt0d||||j |j | } n"#t$j$rt)dwxYw| d}t#||j |d}~wt jj$r#}t#t||d}~wt$j$rt)dt8$r%}t0d|||||d}~wwxYw)z!To be used by RestCLN._request().Nheaders Content-typezapplication/octet-streamzutf-8ztext/plain; charset=utf-8asciiz!application/x-www-form-urlencodeddata)timeout)zNon-json data from CLN: z for code=r*zTimed out reading responseiz,CLN.post(url=%r, data=%r, headers=%r): %d %szTimed out reading error messagereplace)errors)r+zTimed out receiving responsez5CLN.post(url=%r, data=%r, headers=%r, timeout=%r): %s) isinstancebytes setdefaultrencodeurllibparserrequesturlopenRequestcodereadjsonloadsr#JSONDecodeErrorr'socketrb TimeoutErrorr HTTPErrorfprwarningreasonURLErrorOSError) r=rar^rbkwargsrespcontentrr+ resp_datas r _post_requestrtsX F#y  dE " "    N,FG    c " " ;;w''D   N,GH    <))$//66w??D   !DE   v?*~%% N "3 1 1& 1 17&  R * *yCy$ * * * * * * * *j((!"iikkG !#y$*W^^5E5E*F*FF * * * * * * * * /!!!&!57!5!5)-!5!5$(9  ! !! ~EEE&'CDDDEty)))+ * * * * * * * * * *M < !>>> 6C<<16"" ) 4  NN>     FFFHH > F F F"#DEEE F &&i&88Gwqv666A= < ...s1vv&&&A- >;;;9:::     C          s 6G:G-& G-0F7,E>>F4 "F//F44G-7GG--G14G1:M AK!I65K6J0KM K::*M $ MM cjeZdZdZdZejddZejddZ e ejdeZ e e dZ e e dZe e d Ze e d Ze e d Ze e d Zd ZdZeddeddZedZededefdZe ddededefdZededefdZedefdZededefdZ eddZ!dS)RestCLNzhttps://{domain}/api/im/zcln.cloudlinux.comIM360_CLN_API_BASE_URLzipv6.cln.cloudlinux.comzipv4.cln.cloudlinux.comdomainregister unregistercheckinzab/credentialsz ab/removezab/checkokzok-trialN)rar^rbcrKtjdt||||d{VSr))asyncioget_event_looprun_in_executorr)clsr=rar^rbs r_requestzRestCLN._requestsR+--== -dGW         r.cKt|j|jd}dt d} |||d{V\}}ns#t $rf}|jdkrOt|j|jd}|||d{V\}}n|Yd}~nd}~wwxYw|S)NrrIPLkeyhostnamerai) r_URL_PATH_TEMPLATEr0_IPV4_DOMAIN_NAMErrr'r _IPV6_DOMAIN_NAME)rv4_license_urlra_token cln_errorv6_license_urls rprocess_ipl_licencezRestCLN.process_ipl_licences!  " ) )1F ) G G   ,..99  \\.t\DDDDDDDDHAuu 3&&!(*11"42 """%n4!H!HHHHHHH555555  s A$$ C.ACCrrcK|dkr|d{VS||j|tdd{V\}}|S)z Register server with key :param key: registration key :return: license token in case of success rNrr)rr _REGISTER_URLr)rrrrs rrzRestCLN.registers %<<0022222222 2  ,..99&        5 r. server_id users_countrc"K|p t}td{V}t} |d{V}nH#t$r;}t dt|d|j}Yd}~nd}~wwxYw|||||dtd{Vidd}tj |} t d| | |j| d d i d{V\} } | S) z Update license token :param str server_id: server id :param int users_count: users count :param str hostname: current server hostname :return: dict new license token NzFailed to get panel version: %sT)exc_infoIM_EMAIL)userspanel imunifyEmailsupported_features)idrimzCLN checkin: %sr_zapplication/json)rar^)rr%r name ExceptionrrrNAMErrsdumpsinfor _CHECKIN_URL) rrrrimunify_email_statusr panel_namerreqrarrs rrzRestCLN.checkins~-|~~%=%?%??????? $$zz||++++++JJ $ $ $ LL13q66D    JJJJJJ  $ $# 4&@&B&B B B B B B B'    z# %t,,,  #%78&        5  sA B1BBcXK||jd|id{V\}}|S)zl Creates Acronis Backup account and get user & password :param server_id: server id rrN)r_ACRONIS_CREDENTIALS_URL)rrrcredss racronis_credentialszRestCLN.acronis_credentials<sV   (i/@&        5 r.cRK||jd|id{VdS)zT Removes Acronis Backup account :param server_id: server id rrN)r_ACRONIS_REMOVE_URLrrs racronis_removezRestCLN.acronis_removeGs> ll32$ 9JlKKKKKKKKKKKr.cK||jd|id{V\}}|dkrtd|S)z If Acronis account exists return backup size in GB or if backups not exists URL for backups :param server_id: server id rrNrer<)r_ACRONIS_CHECK_URLr:)rrr responses r acronis_checkzRestCLN.acronis_checkOsq"%  "$ ):"." "        S== T*** *r.c|K|ptj}||jd|id{VdS)z< Unregister server id :return: None rrN)r get_server_idr_UNREGISTER_URLrs rrzRestCLN.unregister]sQ ;!9!;!; ll3.dI5FlGGGGGGGGGGGr.r))"r3r4r5r_BASE_DOMAIN_NAMEosenvirongetrrr0 _BASE_URLrrrrrrrSTATUS_OK_PAID_LICENSESTATUS_OK_TRIAL_LICENSE classmethod_TIMEOUTrrrrArintrrrrrr6r.rrrsk3,  ";  ";#))z~~68IJJ*IGIz22Mgi66O79i00L&wy2BCC!')[99 J77!()-tX    [ [,     [   ++++ +++[+Z#$[LSLLL[L C D   [ HHH[HHHr.rceZdZeeZedZedZedZ edZ edZ edZ dS)CLNcF|j||dSr)) _CALLBACKSrV)r method_name coro_callbacks radd_callback_forzCLN.add_callback_forjs# {#'' 66666r.c K|j|D]h} |d{V#tj$rt$r9}td|||Yd}~ad}~wwxYwdS)Nz;Error '{!r}' happened when run callback {} forCLN {} method)rrCancelledErrorrr exceptionr0)rrcallbackrs rrun_callbacks_forzCLN.run_callbacks_forns{3  H hjj        )        $$*F1h $D$D   s$A6/A11A6c,|dS)NIMAVP)rR)rrs r is_avp_keyzCLN.is_avp_key{s~~g&&&r.cK||rtstdt|d{V}t j|s5t|dd{Vtdt j|| dd{VdS)Nz4Imunify360 can not be registered with ImunifyAV+ keyrz"License is invalid for this serverr) rr r8rrr is_validrupdater)rrlicenses rrz CLN.registers >>#   ~ %F  ((--------"7++ L$$WT]33 3 3 3 3 3 3 3%&JKK K'"""##J///////////r.cKtd{Vtj|dd{VdS)Nr)rrr deleter)rs rrzCLN.unregisterse  """""""""##L11111111111r.c$KtjrtjStjdrtjSt|dtjd{V}td|| t d{Vntj || dd{VtjS)z>Refreshes token and returns new one on success, None otherwiseis_alternativerNzGot new token from CLN: %s refresh_token) r is_free get_tokenrrrrrrrrrr)rr new_tokens rrzCLN.refresh_tokens      *')) )   ! ! % %&6 7 7 *')) )!//%+z7MNNNNNNNN  0)<<<  .."" " " " " " " " "  i ( ( (##O444444444#%%%r.N) r3r4r5rrOrrrrrrrrr6r.rrrgsS!!J77[7  [ ''[' 0 0[ 022[2 &&[&&&r.rcHdD]}t||dS)N)rrr)r)rr)corors rsubscribe_to_license_changesrs7B>>  [====>>r.)NNN)0rrsloggingrrv urllib.errorrl urllib.parseurllib.request collectionsrpathlibrrrrrr rM defence360agent.contracts.configr !defence360agent.contracts.licenser +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr rrdefence360agent.utils.commonrrr! getLoggerr3rrboolrr%rr'r8r:rrrrr6r.rrs  ######LLLLLLLLLLLLLL ;;;;;;888888DDDDDDKKKKKKKKKK555555 #'4(=#>#>  8 $ $ $ G G G I I I I Iy I I I     )   (+(+(+(+(+X(+(+(+VU*U*U*U*pXHXHXHXHXHXHXHXHv@&@&@&@&@&@&@&@&F>>>>>r.defence360agent/internals/__pycache__/deadlock_detecting_lock.cpython-311.opt-1.pyc0000644000000000000000000000411600000000000025224 0ustar r_jDddlZGddeZGddZdS)NceZdZdZdS) DeadlockErrorz6Error raised if DeadlockDetectingLock detects deadlockN)__name__ __module__ __qualname____doc__f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/deadlock_detecting_lock.pyrrs@@@@r rc*eZdZdZdZdZdZdZdS)DeadlockDetectingLockzp Lock that detects deadlock when it is about to be acquired by the same task that already holds it. cDtj|_d|_dSN)asyncioLock_lock_ownerselfs r __init__zDeadlockDetectingLock.__init__s\^^  r c4|jSr)rlockedrs r rzDeadlockDetectingLock.lockedsz  """r cKtj}|j|krt|jd{V||_|Sr)r current_taskrrracquire)r curr_tasks r __aenter__z DeadlockDetectingLock.__aenter__s](** ;) # #// !j  """""""""  r cJKd|_|jdSr)rrrelease)rexc_typeexctbs r __aexit__zDeadlockDetectingLock.__aexit__s'  r N)rrrrrrrr#r r r r r sZ ###r r )r Exceptionrr r r r r%srAAAAAIAAAr defence360agent/internals/__pycache__/deadlock_detecting_lock.cpython-311.pyc0000644000000000000000000000411600000000000024265 0ustar r_jDddlZGddeZGddZdS)NceZdZdZdS) DeadlockErrorz6Error raised if DeadlockDetectingLock detects deadlockN)__name__ __module__ __qualname____doc__f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/deadlock_detecting_lock.pyrrs@@@@r rc*eZdZdZdZdZdZdZdS)DeadlockDetectingLockzp Lock that detects deadlock when it is about to be acquired by the same task that already holds it. cDtj|_d|_dSN)asyncioLock_lock_ownerselfs r __init__zDeadlockDetectingLock.__init__s\^^  r c4|jSr)rlockedrs r rzDeadlockDetectingLock.lockedsz  """r cKtj}|j|krt|jd{V||_|Sr)r current_taskrrracquire)r curr_tasks r __aenter__z DeadlockDetectingLock.__aenter__s](** ;) # #// !j  """""""""  r cJKd|_|jdSr)rrrelease)rexc_typeexctbs r __aexit__zDeadlockDetectingLock.__aexit__s'  r N)rrrrrrrr#r r r r r sZ ###r r )r Exceptionrr r r r r%srAAAAAIAAAr defence360agent/internals/__pycache__/delivery_ack.cpython-311.opt-1.pyc0000644000000000000000000000514200000000000023061 0ustar r_jpdZddlZddlmZmZejeZGddZeZ dS)amIn-memory delivery acknowledgements for Reportable messages. The send-to-server plugins queue messages rather than deliver them, so a producer that keeps its own copy of the payload cannot tell a delivered message from one a lost send round dropped. Every send path reports the ids the transport accepted here, and producers register the ids they care about. Acknowledgements are deliberately not persisted: one that never arrives leaves the message unconfirmed and makes the producer send it again. That costs a duplicate the server may well store twice, whereas a silently dropped payload cannot be recovered at all. N)CallableOptionalcfeZdZd dZdedegdfddfdZdeddfdZdeeddfdZ dS) DeliveryAckRegistryreturnNci|_dSN _callbacks)selfs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/delivery_ack.py__init__zDeliveryAckRegistry.__init__s 9; message_id on_deliveredc"|sdS||j|<dSr r r rrs r watchzDeliveryAckRegistry.watchs!  F&2 ###rc<|j|ddSr )r pop)r rs r unwatchzDeliveryAckRegistry.unwatchs  J-----rc|j|d}|dS |dS#t$rtd|YdSwxYw)Nz&Delivery acknowledgement for %s failed)r r Exceptionlogger exceptionrs r confirmzDeliveryAckRegistry.confirm s**:t<<   F  LNNNNN      8*        s -%AA)rN) __name__ __module__ __qualname__rstrrrrrrrr rrs<<<<338BH3E3$3333 .#.$.... (3- D      rr) __doc__loggingtypingrr getLoggerrrrregistryr!rr r's  %%%%%%%%  8 $ $6   rdefence360agent/internals/__pycache__/delivery_ack.cpython-311.pyc0000644000000000000000000000514200000000000022122 0ustar r_jpdZddlZddlmZmZejeZGddZeZ dS)amIn-memory delivery acknowledgements for Reportable messages. The send-to-server plugins queue messages rather than deliver them, so a producer that keeps its own copy of the payload cannot tell a delivered message from one a lost send round dropped. Every send path reports the ids the transport accepted here, and producers register the ids they care about. Acknowledgements are deliberately not persisted: one that never arrives leaves the message unconfirmed and makes the producer send it again. That costs a duplicate the server may well store twice, whereas a silently dropped payload cannot be recovered at all. N)CallableOptionalcfeZdZd dZdedegdfddfdZdeddfdZdeeddfdZ dS) DeliveryAckRegistryreturnNci|_dSN _callbacks)selfs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/delivery_ack.py__init__zDeliveryAckRegistry.__init__s 9; message_id on_deliveredc"|sdS||j|<dSr r r rrs r watchzDeliveryAckRegistry.watchs!  F&2 ###rc<|j|ddSr )r pop)r rs r unwatchzDeliveryAckRegistry.unwatchs  J-----rc|j|d}|dS |dS#t$rtd|YdSwxYw)Nz&Delivery acknowledgement for %s failed)r r Exceptionlogger exceptionrs r confirmzDeliveryAckRegistry.confirm s**:t<<   F  LNNNNN      8*        s -%AA)rN) __name__ __module__ __qualname__rstrrrrrrrr rrs<<<<338BH3E3$3333 .#.$.... (3- D      rr) __doc__loggingtypingrr getLoggerrrrregistryr!rr r's  %%%%%%%%  8 $ $6   rdefence360agent/internals/__pycache__/feature_flags.cpython-311.opt-1.pyc0000644000000000000000000003447500000000000023242 0ustar r_j&UdZddlmZddlZddlZddlZddlmZdZdZ dZ dZ ia d e d <iad e d <ead e d<dade d<d1dZd2dZd3dZd4dZd5dZd6dZd7d Zd8d"Zd9d%Zd7d&Zd8d'Zd:d(Zd:d)Zd;dd0Z!dS)?av Shared reader for the local feature flags file. The file is written by: - Go resident-agent FeatureFlags plugin (IM360 mode) - Python FeatureFlagsSync plugin (AV mode) Other subsystems (e.g. message_status_publisher) use this module to check individual flag values at runtime. Supported JSON shapes on disk (readers / ``is_enabled`` / ``get_params``): - New shape ``{"flags": ["mqtt_tracking"], "params": {"flag": ["A", "B"]}}`` (mirrors the sync API response; carries per-flag string-list params). - Legacy object ``{"mqtt_tracking": true, ...}`` (still accepted). - JSON array of enabled names ``["mqtt_tracking"]`` (still accepted). - Legacy wrapper ``{"flags": ["mqtt_tracking", ...]}`` (still accepted). The sync API checksum collapses to the legacy sorted-names array when no params are present, so this agent and older agents agree on the bool-only case. With params, the canonical form expands to ``{"flags": [...], "params": {...}}`` with all keys and list members sorted. The sync plugin also writes ``FLAGS_PLAIN_PATH`` (``/var/imunify360/feature_flags``): plain text, one enabled flag name per line (sorted), for scripts. ) annotationsN)Anyz"/var/imunify360/feature_flags.jsonz/var/imunify360/feature_flagsmqtt_tracked_methodsmessage_loss_observabilitydict[str, Any] _cached_flagsdict[str, list[str]]_cached_paramsfrozenset[str]_cached_mqtt_methodsfloat _cached_mtimerawrreturnc|iSt|tr#i}|D]}t|trd||<|St|tr;|d}t|trt |S|SiS)z@Map file JSON to a flat name->value dict for :func:`is_enabled`.NTflags) isinstanceliststrdictget_normalize_flags_from_file)routiteminners \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/feature_flags.pyrr8s { #t  ! !D$$$ ! D  #t   eT " " 5-e44 4 Ic@t|tsiS|d}t|tsiSi}|D]C\}}t|trt|t s0d|D}|r|||<D|S)zExtract ``params`` mapping from new-shape file content. Only the new ``{"flags": [...], "params": {name: [...]}}`` shape carries params; every other (legacy) shape returns an empty mapping. paramsc<g|]}t|t|Srr).0vs r z%_params_from_file..Ys';;; 1c(:(:;1;;;r)rrritemsrr)r raw_paramsrnamevaluescleaneds r_params_from_filer,Js c4  ""J j$ ' ' "C"((**  f$$$ Jvt,D,D  ;;f;;;  CI Jr+tuple[dict[str, Any], dict[str, list[str]]]c^ tjt}n2#t$r%iaiatada t t fcYSwxYw|tkrt t fS tt5}tj |}dddn #1swxYwYt|at|an #ttjf$riaiaYnwxYwtt t"da|a t t fS)Nr r")ospathgetmtime FLAGS_PATHOSErrorrr frozensetr ropenjsonloadrr,JSONDecodeErrorrMQTT_TRACKED_METHODS_FLAG)mtimefrs r _read_stater<_sr-  ,, --- ({{ n,,,, - n,, *   )A,,C               2377 *3// T) * %4b99M . ((sE$',AA3CB( C(B,,C/B,0!CC/.C/c(t\}}|SNr<)r_s r _read_flagsrA|s}}HE1 Lrc(t\}}|Sr>r?)r@r s r _read_paramsrCs IAv Mrr list[str]ct|ttfs$tdt |jt |}td|DS)aReturn sorted enabled flag names for JSON and plain-text sidecar. Accepts the same shapes as :func:`_normalize_flags_from_file` (array, flat map, ``{"flags": [...]}``) so checksums and sidecars match Go ``enabledNamesSortedForChecksum`` / :func:`is_enabled`. z flags must be list or dict, not c3$K|] \}}||V dSr>r"r$kr%s r z,enabled_flag_names_sorted..s+881a8!888888r) rrr TypeErrortype__name__rsortedr')r normalizeds renabled_flag_names_sortedrOsx edD\ * *  EtE{{/C E E   ,E22J 88 0 0 2 2888 8 88rnamesbytescrt|}tj|ddS)z|JSON array bytes used for sync MD5 when no params are present (matches correlation_api ``checksum_for_sync_flag_list``).T sort_keysindentrMr6dumpsencode)rPordereds rcanonical_sync_flag_list_bytesr[s2UmmG :ga 8 8 8 ? ? A AArr c|st|St|dt|Dd}tj|ddS)aeJSON bytes for the sync MD5 over the full response shape. Mirrors correlation_api ``checksum_for_sync_response``: collapses to the legacy sorted-names array when ``params`` is empty so old agents keep matching, otherwise expands to the deterministic ``{"flags": [...], "params": {...}}`` form with all keys and list members sorted. c4i|]\}}|t|Sr"rMrGs r z1canonical_sync_response_bytes..$CCCDAq1fQiiCCCrrr TrSrT)r[rMr'r6rXrYrPr canonicals rcanonical_sync_response_bytesrdsu 5-e444CCF6<<>>,B,BCCCI :i4 : : : A A C CCrr0rcr t|d5}tj|}dddn #1swxYwYn##tttjf$rYdSwxYwt |}t|}t||}tj |d S)zMD5 hex of the canonical sync-response form for ``path``. Returns "" if the file is missing or invalid. Computes the same MD5 the server returned, so a matching checksum lets the agent skip the response payload on the next sync. zutf-8)encodingNF)usedforsecurity) r5r6r7r3UnicodeDecodeErrorr8rOr,rdhashlibmd5 hexdigest)r0r;rrPr payloads r!sync_checksum_hex_from_flags_filerns $ ) ) ) Q)A,,C                ')= >rr %c * *E s # #F+E6::G ;w 6 6 6 @ @ B BBs,A4 A8A8AA A cdtd|DD}tj|ddS)z.sKKKQDKKKrc<h|]}t|t|Sr"r#)r$xs r z1legacy_feature_flags_map_bytes..s'!I!I!IjC6H6H!I!!I!I!IrTrSrTrW)rPds rlegacy_feature_flags_map_bytesrvsOKK&!I!IU!I!I!IJJKKKA :a4 2 2 2 9 9 ; ;;rct|dt|Dd}tj|ddS)zPersisted form for ``FLAGS_PATH`` carrying both flags and params. Same canonical shape as ``canonical_sync_response_bytes`` so the file is self-describing and round-trips through ``sync_checksum_hex_from_flags_file``. c4i|]\}}|t|Sr"r^rGs rr_z,sync_response_file_bytes..r`rraTrSrT)rMr'r6rXrYrbs rsync_response_file_bytesrys_CCF6<<>>,B,BCCCI :i4 : : : A A C CCrc|t|}|sdSd|dzS)zPBody for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.r )rOjoinrY)rrPs r$plain_text_payload_for_enabled_flagsr}s? %e , ,E s IIe  t # + + - --rct|tr)tj|ddSt dt |j)zBSerialize dict flags for writing ``FLAGS_PATH`` (legacy map only).TrSrTzflags must be dict, not )rrr6rXrYrJrKrL)rs r$serialize_feature_flags_file_payloadrsX%Dz%4:::AACCC EtE{{/CEE F FFrF flag_namedefaultboolcnt}||}||St|S)zReturn whether *flag_name* is enabled. If the file is missing, unreadable, or the flag is absent, *default* is returned. Defaults to False so unknown flags are treated as disabled unless the caller explicitly opts in. )rArr)rrrvalues r is_enabledrs4 MME IIi E } ;;rc`tt|dS)zReturn the per-flag string params from the on-disk file. Empty list when the file is missing/unreadable, the flag is unknown, or the value did not come from the new structured shape (legacy bool-only flags carry no params by definition). r")rrCr)rs r get_paramsrs&  ""9b11 2 22rc,ttS)uFrozen set of method names whose status events should be enriched for MQTT tracing. Driven entirely by the server-side ``mqtt_tracked_methods`` flag's params list — the agent has no hard-coded list, so adding/removing tracked types is a server-side config change with no agent rollout. Cached: ``_read_state`` pre-builds the frozenset and invalidates it when the flags file's mtime changes. On the hot path — every Reportable message in ``the_sink._call_unlocked`` — this is a single ``os.stat`` syscall plus an identity-stable frozenset return. Two consecutive calls within the same mtime window return the same instance. )r<r r"rrrrsMMM r)rrrr)rrrr )rr-)rr)rr )rrrrD)rPrDrrQ)rPrDr r rrQ)r0rrr)rrrrQ)F)rrrrrr)rrrrD)rr )"__doc__ __future__rrjr6r/typingrr2FLAGS_PLAIN_PATHr9MESSAGE_LOSS_OBSERVABILITY_FLAGr__annotations__r r4r rrr,r<rArCrOr[rdrnrvryr}rrrrr"rrrs 4#"""""  1 23#? " """"'))))) (1y{{2222 $*)))):  9 9 9 9BBBBDDDD(CCCC$<<<< D D D D....GGGG     3333      rdefence360agent/internals/__pycache__/feature_flags.cpython-311.pyc0000644000000000000000000003447500000000000022303 0ustar r_j&UdZddlmZddlZddlZddlZddlmZdZdZ dZ dZ ia d e d <iad e d <ead e d<dade d<d1dZd2dZd3dZd4dZd5dZd6dZd7d Zd8d"Zd9d%Zd7d&Zd8d'Zd:d(Zd:d)Zd;dd0Z!dS)?av Shared reader for the local feature flags file. The file is written by: - Go resident-agent FeatureFlags plugin (IM360 mode) - Python FeatureFlagsSync plugin (AV mode) Other subsystems (e.g. message_status_publisher) use this module to check individual flag values at runtime. Supported JSON shapes on disk (readers / ``is_enabled`` / ``get_params``): - New shape ``{"flags": ["mqtt_tracking"], "params": {"flag": ["A", "B"]}}`` (mirrors the sync API response; carries per-flag string-list params). - Legacy object ``{"mqtt_tracking": true, ...}`` (still accepted). - JSON array of enabled names ``["mqtt_tracking"]`` (still accepted). - Legacy wrapper ``{"flags": ["mqtt_tracking", ...]}`` (still accepted). The sync API checksum collapses to the legacy sorted-names array when no params are present, so this agent and older agents agree on the bool-only case. With params, the canonical form expands to ``{"flags": [...], "params": {...}}`` with all keys and list members sorted. The sync plugin also writes ``FLAGS_PLAIN_PATH`` (``/var/imunify360/feature_flags``): plain text, one enabled flag name per line (sorted), for scripts. ) annotationsN)Anyz"/var/imunify360/feature_flags.jsonz/var/imunify360/feature_flagsmqtt_tracked_methodsmessage_loss_observabilitydict[str, Any] _cached_flagsdict[str, list[str]]_cached_paramsfrozenset[str]_cached_mqtt_methodsfloat _cached_mtimerawrreturnc|iSt|tr#i}|D]}t|trd||<|St|tr;|d}t|trt |S|SiS)z@Map file JSON to a flat name->value dict for :func:`is_enabled`.NTflags) isinstanceliststrdictget_normalize_flags_from_file)routiteminners \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/feature_flags.pyrr8s { #t  ! !D$$$ ! D  #t   eT " " 5-e44 4 Ic@t|tsiS|d}t|tsiSi}|D]C\}}t|trt|t s0d|D}|r|||<D|S)zExtract ``params`` mapping from new-shape file content. Only the new ``{"flags": [...], "params": {name: [...]}}`` shape carries params; every other (legacy) shape returns an empty mapping. paramsc<g|]}t|t|Srr).0vs r z%_params_from_file..Ys';;; 1c(:(:;1;;;r)rrritemsrr)r raw_paramsrnamevaluescleaneds r_params_from_filer,Js c4  ""J j$ ' ' "C"((**  f$$$ Jvt,D,D  ;;f;;;  CI Jr+tuple[dict[str, Any], dict[str, list[str]]]c^ tjt}n2#t$r%iaiatada t t fcYSwxYw|tkrt t fS tt5}tj |}dddn #1swxYwYt|at|an #ttjf$riaiaYnwxYwtt t"da|a t t fS)Nr r")ospathgetmtime FLAGS_PATHOSErrorrr frozensetr ropenjsonloadrr,JSONDecodeErrorrMQTT_TRACKED_METHODS_FLAG)mtimefrs r _read_stater<_sr-  ,, --- ({{ n,,,, - n,, *   )A,,C               2377 *3// T) * %4b99M . ((sE$',AA3CB( C(B,,C/B,0!CC/.C/c(t\}}|SNr<)r_s r _read_flagsrA|s}}HE1 Lrc(t\}}|Sr>r?)r@r s r _read_paramsrCs IAv Mrr list[str]ct|ttfs$tdt |jt |}td|DS)aReturn sorted enabled flag names for JSON and plain-text sidecar. Accepts the same shapes as :func:`_normalize_flags_from_file` (array, flat map, ``{"flags": [...]}``) so checksums and sidecars match Go ``enabledNamesSortedForChecksum`` / :func:`is_enabled`. z flags must be list or dict, not c3$K|] \}}||V dSr>r"r$kr%s r z,enabled_flag_names_sorted..s+881a8!888888r) rrr TypeErrortype__name__rsortedr')r normalizeds renabled_flag_names_sortedrOsx edD\ * *  EtE{{/C E E   ,E22J 88 0 0 2 2888 8 88rnamesbytescrt|}tj|ddS)z|JSON array bytes used for sync MD5 when no params are present (matches correlation_api ``checksum_for_sync_flag_list``).T sort_keysindentrMr6dumpsencode)rPordereds rcanonical_sync_flag_list_bytesr[s2UmmG :ga 8 8 8 ? ? A AArr c|st|St|dt|Dd}tj|ddS)aeJSON bytes for the sync MD5 over the full response shape. Mirrors correlation_api ``checksum_for_sync_response``: collapses to the legacy sorted-names array when ``params`` is empty so old agents keep matching, otherwise expands to the deterministic ``{"flags": [...], "params": {...}}`` form with all keys and list members sorted. c4i|]\}}|t|Sr"rMrGs r z1canonical_sync_response_bytes..$CCCDAq1fQiiCCCrrr TrSrT)r[rMr'r6rXrYrPr canonicals rcanonical_sync_response_bytesrdsu 5-e444CCF6<<>>,B,BCCCI :i4 : : : A A C CCrr0rcr t|d5}tj|}dddn #1swxYwYn##tttjf$rYdSwxYwt |}t|}t||}tj |d S)zMD5 hex of the canonical sync-response form for ``path``. Returns "" if the file is missing or invalid. Computes the same MD5 the server returned, so a matching checksum lets the agent skip the response payload on the next sync. zutf-8)encodingNF)usedforsecurity) r5r6r7r3UnicodeDecodeErrorr8rOr,rdhashlibmd5 hexdigest)r0r;rrPr payloads r!sync_checksum_hex_from_flags_filerns $ ) ) ) Q)A,,C                ')= >rr %c * *E s # #F+E6::G ;w 6 6 6 @ @ B BBs,A4 A8A8AA A cdtd|DD}tj|ddS)z.sKKKQDKKKrc<h|]}t|t|Sr"r#)r$xs r z1legacy_feature_flags_map_bytes..s'!I!I!IjC6H6H!I!!I!I!IrTrSrTrW)rPds rlegacy_feature_flags_map_bytesrvsOKK&!I!IU!I!I!IJJKKKA :a4 2 2 2 9 9 ; ;;rct|dt|Dd}tj|ddS)zPersisted form for ``FLAGS_PATH`` carrying both flags and params. Same canonical shape as ``canonical_sync_response_bytes`` so the file is self-describing and round-trips through ``sync_checksum_hex_from_flags_file``. c4i|]\}}|t|Sr"r^rGs rr_z,sync_response_file_bytes..r`rraTrSrT)rMr'r6rXrYrbs rsync_response_file_bytesrys_CCF6<<>>,B,BCCCI :i4 : : : A A C CCrc|t|}|sdSd|dzS)zPBody for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.r )rOjoinrY)rrPs r$plain_text_payload_for_enabled_flagsr}s? %e , ,E s IIe  t # + + - --rct|tr)tj|ddSt dt |j)zBSerialize dict flags for writing ``FLAGS_PATH`` (legacy map only).TrSrTzflags must be dict, not )rrr6rXrYrJrKrL)rs r$serialize_feature_flags_file_payloadrsX%Dz%4:::AACCC EtE{{/CEE F FFrF flag_namedefaultboolcnt}||}||St|S)zReturn whether *flag_name* is enabled. If the file is missing, unreadable, or the flag is absent, *default* is returned. Defaults to False so unknown flags are treated as disabled unless the caller explicitly opts in. )rArr)rrrvalues r is_enabledrs4 MME IIi E } ;;rc`tt|dS)zReturn the per-flag string params from the on-disk file. Empty list when the file is missing/unreadable, the flag is unknown, or the value did not come from the new structured shape (legacy bool-only flags carry no params by definition). r")rrCr)rs r get_paramsrs&  ""9b11 2 22rc,ttS)uFrozen set of method names whose status events should be enriched for MQTT tracing. Driven entirely by the server-side ``mqtt_tracked_methods`` flag's params list — the agent has no hard-coded list, so adding/removing tracked types is a server-side config change with no agent rollout. Cached: ``_read_state`` pre-builds the frozenset and invalidates it when the flags file's mtime changes. On the hot path — every Reportable message in ``the_sink._call_unlocked`` — this is a single ``os.stat`` syscall plus an identity-stable frozenset return. Two consecutive calls within the same mtime window return the same instance. )r<r r"rrrrsMMM r)rrrr)rrrr )rr-)rr)rr )rrrrD)rPrDrrQ)rPrDr r rrQ)r0rrr)rrrrQ)F)rrrrrr)rrrrD)rr )"__doc__ __future__rrjr6r/typingrr2FLAGS_PLAIN_PATHr9MESSAGE_LOSS_OBSERVABILITY_FLAGr__annotations__r r4r rrr,r<rArCrOr[rdrnrvryr}rrrrr"rrrs 4#"""""  1 23#? " """"'))))) (1y{{2222 $*)))):  9 9 9 9BBBBDDDD(CCCC$<<<< D D D D....GGGG     3333      rdefence360agent/internals/__pycache__/geo.cpython-311.opt-1.pyc0000644000000000000000000000657300000000000021203 0ustar r_j ~ddlmZddlmZmZmZmZddlmZddl m Z ddl m Z GddZ edZd S) )contextmanager) IPv4Address IPv4Network IPv6Address IPv6Network)Union) CountryInfo)IPczeZdZdZdeeeeee ffdZ deeeeee ffdZ deeeeee ffdZ dS)Readerc||_dS)N)_geoip2_reader)self geoip2_readers R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/geo.py__init__zReader.__init__ s+addresscddlm} tj|}n#t$rYdSwxYw |jt|j}n #|$rYdSwxYw|r|jndS)z Returns geo country information from max mind's db request :param address: ip or network address e.g. '4.4.4.4, 1.2.0.0/16, 2001:678:4c::/48' :return: maxmind's geo info r)AddressNotFoundErrorN) geoip2.errorsrr adopt_to_ipvX_network ValueErrorrcountrystrnetwork_address)rrripobjs rgetz Reader.get s 766666 )'22BB   44  %--c"2D.E.EFFCC#   44 "+s{{t+s ++,AA%$A%cB||}|r|jSdS)za :param address: valid ipv4 address :return: maxmind's id of the country N)r geoname_idrr country_infos rget_idz Reader.get_id,s+xx((  +* *trcB||}|r|jSdS)ze :param address: valid ipv4 address :return: country code in ISO-3166 format N)riso_coder"s rget_codezReader.get_code;s+xx((  )( (trN) __name__ __module__ __qualname__rrrrrrrrr$r'rrr r s,,,, k; C ,,,,>  k; C       k; C       rr c#Kddl}|jtj5}t|VddddS#1swxYwYdS)zH :return Reader obj: instance to be reused to it's method calls rN)geoip2.databasedatabaser r DB)geoip2rs rreaderr1Ks      / /$=]#####$$$$$$$$$$$$$$$$$$sA  AAN) contextlibr ipaddressrrrrtypingr defence360agent.contracts.configr defence360agent.utils.validater r r1r+rrr7s%%%%%%HHHHHHHHHHHH888888------????????D$$$$$rdefence360agent/internals/__pycache__/geo.cpython-311.pyc0000644000000000000000000000657300000000000020244 0ustar r_j ~ddlmZddlmZmZmZmZddlmZddl m Z ddl m Z GddZ edZd S) )contextmanager) IPv4Address IPv4Network IPv6Address IPv6Network)Union) CountryInfo)IPczeZdZdZdeeeeee ffdZ deeeeee ffdZ deeeeee ffdZ dS)Readerc||_dS)N)_geoip2_reader)self geoip2_readers R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/geo.py__init__zReader.__init__ s+addresscddlm} tj|}n#t$rYdSwxYw |jt|j}n #|$rYdSwxYw|r|jndS)z Returns geo country information from max mind's db request :param address: ip or network address e.g. '4.4.4.4, 1.2.0.0/16, 2001:678:4c::/48' :return: maxmind's geo info r)AddressNotFoundErrorN) geoip2.errorsrr adopt_to_ipvX_network ValueErrorrcountrystrnetwork_address)rrripobjs rgetz Reader.get s 766666 )'22BB   44  %--c"2D.E.EFFCC#   44 "+s{{t+s ++,AA%$A%cB||}|r|jSdS)za :param address: valid ipv4 address :return: maxmind's id of the country N)r geoname_idrr country_infos rget_idz Reader.get_id,s+xx((  +* *trcB||}|r|jSdS)ze :param address: valid ipv4 address :return: country code in ISO-3166 format N)riso_coder"s rget_codezReader.get_code;s+xx((  )( (trN) __name__ __module__ __qualname__rrrrrrrrr$r'rrr r s,,,, k; C ,,,,>  k; C       k; C       rr c#Kddl}|jtj5}t|VddddS#1swxYwYdS)zH :return Reader obj: instance to be reused to it's method calls rN)geoip2.databasedatabaser r DB)geoip2rs rreaderr1Ks      / /$=]#####$$$$$$$$$$$$$$$$$$sA  AAN) contextlibr ipaddressrrrrtypingr defence360agent.contracts.configr defence360agent.utils.validater r r1r+rrr7s%%%%%%HHHHHHHHHHHH888888------????????D$$$$$rdefence360agent/internals/__pycache__/global_scope.cpython-311.opt-1.pyc0000644000000000000000000000237600000000000023057 0ustar r_j^ddlZejeZGddeZeZdS)NceZdZdZdZdS) GlobalScopec^ ||S#t$r}t|d|d}~wwxYw)Nz is not in global scope)KeyErrorAttributeError)selfitemerrs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/global_scope.py __getattr__zGlobalScope.__getattr__sN L:  L L L D!A!A!ABB K Ls ,',cR||vrtd|dS|||<dS)Nz"Name %s is already in global scope)loggerwarning)rkeyvalues r __setattr__zGlobalScope.__setattr__ s3 $;; NN? E E E E EDIIIN)__name__ __module__ __qualname__r rrr rrs5LLL rr)logging getLoggerrrdictrgrrr rs[  8 $ $     $   KMMrdefence360agent/internals/__pycache__/global_scope.cpython-311.pyc0000644000000000000000000000237600000000000022120 0ustar r_j^ddlZejeZGddeZeZdS)NceZdZdZdZdS) GlobalScopec^ ||S#t$r}t|d|d}~wwxYw)Nz is not in global scope)KeyErrorAttributeError)selfitemerrs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/global_scope.py __getattr__zGlobalScope.__getattr__sN L:  L L L D!A!A!ABB K Ls ,',cR||vrtd|dS|||<dS)Nz"Name %s is already in global scope)loggerwarning)rkeyvalues r __setattr__zGlobalScope.__setattr__ s3 $;; NN? E E E E EDIIIN)__name__ __module__ __qualname__r rrr rrs5LLL rr)logging getLoggerrrdictrgrrr rs[  8 $ $     $   KMMrdefence360agent/internals/__pycache__/iaid.cpython-311.opt-1.pyc0000644000000000000000000005225300000000000021333 0ustar r_jF9.ddlZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZdd lmZdd lmZmZdd lmZdd lmZdd lmZm Z e e!Z"dZ#dZ$ dZ%Gdde&Z'GddeZ(dS)N) dataclass) getLogger)Path)Callable)urljoin)Request)APIAPIError) LicenseCLN)atomic_rewritesafe_cancel_task)DAY)g)DeadlockDetectingLock DeadlockError <ceZdZdZdS)IAIDTokenErrorz$Can't get iaid token for any reason.N)__name__ __module__ __qualname____doc__S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/iaid.pyrr&s....rrceZdZdZeejedZeejedZ eejedZ eejedZ e dZ e dz Ze dz Ze d z Ze d z Zgggd ZeZejZed GddZedddefdZedddefdZedZedZ edZ!edZ"ed)dZ#edZ$edZ%edefdZ&ed Z'ed*d#Z(ed$Z)ed%Z*ed+d&Z+ed'Z,ed+d(Z-dS),IndependentAgentIDAPIz/api/auth/agent/{}registeractivateloginz token-infoz/var/imunify360iaidz iaid-passwordz iaid-tokenziaid-activated)r r!r"T)frozencJeZdZUgdZeed<eed<eed<eed<eed<dS)IndependentAgentIDAPI.TokenInfo)validr#license_status server_id need_renewr'r#r(r)r*N)rrr __slots__bool__annotations__strrrr TokenInfor&>sX     rr/r)timeoutcorocKtj|tjd|ztzzd{V||d{VdS)N)asynciosleeprandom randrange_TIMEOUT_MULTIPLICATOR)r1attemptr0argss r_retry_on_errorz%IndependentAgentIDAPI._retry_on_errorMs}m f&qG|447MM M         dDkrc fd|j|D|j|<t|j|dkrXtj}|j|||j|g|R||ddStd|dS)Nc:g|]}||Sr)done).0tasks r z3IndependentAgentIDAPI._add_task..Ws5   TYY[[    rr3r9r0zTask %s already in retry queue) _taskslenr4get_event_loopappend create_taskr;loggerinfo)clstyper1r9r0r:loops r _add_taskzIndependentAgentIDAPI._add_taskUs   Z-    4 sz$ A % %)++D Jt  # #  'C'#-4g      KK8$ ? ? ? ? ?rc@|d|jddS)Nr!rr9)rMr!rJs radd_initial_taskz&IndependentAgentIDAPI.add_initial_taskfs" j#, :::::rcK|jD]N\}}|D]F}|s0t|d{Vtd|GOdS)NzRetry task %s was canceled.)rCitemsr>r rHrI)rJrKtasksr@s rshutdownzIndependentAgentIDAPI.shutdownjs:++-- E EKD% E Eyy{{E*4000000000KK =tDDD E E Erc4tjdjS)N_imunify)grpgetgrnamgr_gidrrr_gidzIndependentAgentIDAPI._gidrs|J''..rcj|jr|jSdSN) IAID_FILEexists read_textrPs rget_iaidzIndependentAgentIDAPI.get_iaidvs1 =   ! ! -=**,, ,trNPOSTc ddi}|||t||||r&tj|ndS)Nz Content-Typezapplication/json)methodheadersdata)updaterjsondumpsencode)urlrerdkwargs_headerss r_requestzIndependentAgentIDAPI._request|sh"$67   OOG $ $ $ 06@F##**,,,D     rcLtd|j|jfDS)Nc3>K|]}|VdSr])r_)r? iaid_files r z6IndependentAgentIDAPI.is_registered..sB             r)allr^IAID_PASSWORD_FILErPs r is_registeredz#IndependentAgentIDAPI.is_registereds:  !mS-CD      rcKtrGtd{Vtrtd |jd}|std|S#t$r}td||d}~wwxYw)zWEnsure that iaid token is up to date Return iaid token or raise IAIDTokenError.NzIAID token is expiredascii)encodingzIAID_TOKEN_FILE is emptyzCan't get iaid token, reason: )ris_token_expiredr"rIAID_TOKEN_FILEr`strip Exception)rJtokenes r get_tokenzIndependentAgentIDAPI.get_tokens ! 1 1 3 3 >'--// / / / / / / /$5577 >$%<=== N'1171CCIIKKE A$%?@@@L N N N !E!!E!EFFA M Ns$?B$$ C.CCreturnclK|d{V}d|i}||j|d}||d{V}|d}|t d| |jdi|S#t$r}t d|d||d}~wwxYw) NzX-AuthGET)rerd token_infozwrong response %rzincomplete token_info z: r)rrn TOKEN_INFO async_requestgetr r/ TypeError)rJ iaid_tokenrerequestresultr}r~s r_get_token_infoz%IndependentAgentIDAPI._get_token_infos==??****** Z(,,s~wu,MM((11111111 <(( =.77 7 O 3=))5)) ) O O O(UUUAAFGGQ N Os B B3B..B3c tj|j}|j}n#t$rd}YnwxYwt j|z t kS)Ng)osstatrzst_mtimeFileNotFoundErrortimer)rJrrs rryz&IndependentAgentIDAPI.is_token_expiredsb %73.//D}HH!   HHH y{{X%++s # 22Fr3c `K|j} |j4d{V|rF|r|r dddd{VdS|,||kr dddd{VdSt }t j}|r||d<|j|jfi|} | |d{V}|j dtt|j|d|jd|dtt|j|d|jd |d{Vn#t&$r} t(d | || j| jd ks | jd kr/|t.kr$|d|j|||dz|n"t(d|j|| Yd} ~ dddd{VdSd} ~ wwxYw dddd{VdS#1d{VswxYwYdS#t8$rt(d|YdSwxYw)Nr)T missing_okr#backupuidgid permissionspasswordi)rrz0Something went wrong on register %r - attempt %sr r3rOz-Failed to register (%s) after %s attempts: %rz>$)  ,,..((((((((W NNJ  -=C//=C//!J.. &L!0#aK$+ & K#,#  FFFgC )C )C )C )C )C )C )C )C )C )C )C )C )C )0V)GC )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )H    LLN       s~ JI1JI12J;I16F*7B3I1* I4B I>I1JII1J1 I;;J>I;?J%J-,J-cPK|js|d{VdStj}|d{V}|j|dks|j|dkr7t d|| d{VdS|j }|jr|j|ks|jr|d{VdSdS)z!Check whether the agent activatedNstatusidzGot a corrupted token: %r)rr_r!r rrr(rr)rHr reactivater^r`r'r#r*r")rJlicr}r#s rensure_is_activated_and_validz3IndependentAgentIDAPI.ensure_is_activated_and_valid sJ&--// ,,.. F"$$))++++++++  377 $ $   _ - - LL4e < < <.."" " " " " " " " F}&&(({ ejD00E4D0))++         10rc>|jjSr])rtrrrPs rrz)IndependentAgentIDAPI._get_credentials_tss%**,,55rc K|jr|td<dS|s6t d|d{VdStj rL|td<| r| d{VdStj }|st ddS|j 4d{V|jr0|td< dddd{VdS|j}|j}|}||j|||}|td<d} ||d{V|j|jd| d{Vn#t0$r}t d|||jr|jd krd}nr|jrI|jd ks |jd kr3|t4kr(|d |j|d z|t:n"t d|j||Yd}~nd}~wwxYwdddd{Vn#1d{VswxYwY|r|d|d{VdSdS)Nr#z&need to register first before activatez9Can't continue iaid activation: no valid license is found)r#rlicenseFTrz.Something went wrong on activate %r attempt %srrr!r3rBz-Failed to activate (%s) after %s attempts: %rrr) rr_rarrurHrr r is_freeryr"r_activate_lockr^r`rtrrn ACTIVATE_URLrtouchrzrr rrrMr!_ACTIVATE_MINIMUM_TIMEOUTrr) rJr9rr#rcredentials_tsrneed_to_registerr~s rr!zIndependentAgentIDAPI.activate!sc  " ) ) + +  AfI F  ""  NNC D D D,,.. F      AfI##%% "iikk!!!!!!! F"$$  NNK    F%2 "2 "2 "2 "2 "2 "2 "2 "&--// LLNN&  2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 " =**,,D-7799H 4466Nll th#GAfI$ $ "''000000000'--///B#**d*;;;iikk!!!!!!!!C   D =Q]c%9%9'+$$M#--#1E1E*,, MM" ! ' 9 "LLG( 3 #2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "f  P,,T,OO O O O O O O O O O P PsD!7L+A0L4I6L K=B#K83L8K==L LLctK|jd|d{VdS)NTr)rrr!rPs rrz IndependentAgentIDAPI.reactivateksF &&$&777llnnrcK|stddS|j}|j}|}||j||} | |d{V}tt|j |d|j d|ddS#t$r}td|||t"kr]|j |jdkr"|d |j|d z| nW|jd kr|d |d{Vn(td|j||Yd}~dSYd}~dSYd}~dSd}~wwxYw)Nz#need to register first before login)r#rr}rrrz/Something wrong happened on login %r attempt %srr"r3rOrTrz*Failed to login (%s) after %s attempts: %r)rurHrr^r`rtrrn LOGIN_URLrr r.rzr_r[r rrrrMr"r r)rJr9r#rrrrr~s rr"zIndependentAgentIDAPI.loginpsS  ""  LL> ? ? ? F}&&(()33550022,,s}4(,KK ,,W55555555F. C'((w*1133HHJJ!       -    NNA1g   ##=(AMS,@,@MMGaK"]c)),,"' @$  sD GB&G  G)Nrb)FNr3)r3).rrrAPI_PATHrr _BASE_URLformatrrrrrIAID_DIRr^rtrzrrCrrr4Lockrrr/ staticmethodrr; classmethodrMrQrUr[rarnrurrryr rrr!rr"rrrrr*s#H73=(//**E*EFFL73=(//**E*EFFL xw'?'?@@I (E(EFFJt%&&H6!I!O3-O"%55F +*,,N!W\^^NYd        FGH\EF@@@8@@@[@ ;;[;EE[E//\/[     \    [  N N[ N Oi O O O[ O,,[,PPP[Pd[$66[6GPGPGP[GPR[((([(((rr))r4rXrhrr6r dataclassesrloggingrpathlibrtypingr urllib.parserurllib.requestrdefence360agent.api.serverr r !defence360agent.contracts.licenser defence360agent.utilsr r defence360agent.utils.commonr&defence360agent.internals.global_scoper1defence360agent.internals.deadlock_detecting_lockrrrrHrr8r RuntimeErrorrrrrrrs !!!!!! """"""44444444888888BBBBBBBB,,,,,,444444 8    /////\///oooooCooooordefence360agent/internals/__pycache__/iaid.cpython-311.pyc0000644000000000000000000005225300000000000020374 0ustar r_jF9.ddlZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZdd lmZdd lmZmZdd lmZdd lmZdd lmZm Z e e!Z"dZ#dZ$ dZ%Gdde&Z'GddeZ(dS)N) dataclass) getLogger)Path)Callable)urljoin)Request)APIAPIError) LicenseCLN)atomic_rewritesafe_cancel_task)DAY)g)DeadlockDetectingLock DeadlockError <ceZdZdZdS)IAIDTokenErrorz$Can't get iaid token for any reason.N)__name__ __module__ __qualname____doc__S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/iaid.pyrr&s....rrceZdZdZeejedZeejedZ eejedZ eejedZ e dZ e dz Ze dz Ze d z Ze d z Zgggd ZeZejZed GddZedddefdZedddefdZedZedZ edZ!edZ"ed)dZ#edZ$edZ%edefdZ&ed Z'ed*d#Z(ed$Z)ed%Z*ed+d&Z+ed'Z,ed+d(Z-dS),IndependentAgentIDAPIz/api/auth/agent/{}registeractivateloginz token-infoz/var/imunify360iaidz iaid-passwordz iaid-tokenziaid-activated)r r!r"T)frozencJeZdZUgdZeed<eed<eed<eed<eed<dS)IndependentAgentIDAPI.TokenInfo)validr#license_status server_id need_renewr'r#r(r)r*N)rrr __slots__bool__annotations__strrrr TokenInfor&>sX     rr/r)timeoutcorocKtj|tjd|ztzzd{V||d{VdS)N)asynciosleeprandom randrange_TIMEOUT_MULTIPLICATOR)r1attemptr0argss r_retry_on_errorz%IndependentAgentIDAPI._retry_on_errorMs}m f&qG|447MM M         dDkrc fd|j|D|j|<t|j|dkrXtj}|j|||j|g|R||ddStd|dS)Nc:g|]}||Sr)done).0tasks r z3IndependentAgentIDAPI._add_task..Ws5   TYY[[    rr3r9r0zTask %s already in retry queue) _taskslenr4get_event_loopappend create_taskr;loggerinfo)clstyper1r9r0r:loops r _add_taskzIndependentAgentIDAPI._add_taskUs   Z-    4 sz$ A % %)++D Jt  # #  'C'#-4g      KK8$ ? ? ? ? ?rc@|d|jddS)Nr!rr9)rMr!rJs radd_initial_taskz&IndependentAgentIDAPI.add_initial_taskfs" j#, :::::rcK|jD]N\}}|D]F}|s0t|d{Vtd|GOdS)NzRetry task %s was canceled.)rCitemsr>r rHrI)rJrKtasksr@s rshutdownzIndependentAgentIDAPI.shutdownjs:++-- E EKD% E Eyy{{E*4000000000KK =tDDD E E Erc4tjdjS)N_imunify)grpgetgrnamgr_gidrrr_gidzIndependentAgentIDAPI._gidrs|J''..rcj|jr|jSdSN) IAID_FILEexists read_textrPs rget_iaidzIndependentAgentIDAPI.get_iaidvs1 =   ! ! -=**,, ,trNPOSTc ddi}|||t||||r&tj|ndS)Nz Content-Typezapplication/json)methodheadersdata)updaterjsondumpsencode)urlrerdkwargs_headerss r_requestzIndependentAgentIDAPI._request|sh"$67   OOG $ $ $ 06@F##**,,,D     rcLtd|j|jfDS)Nc3>K|]}|VdSr])r_)r? iaid_files r z6IndependentAgentIDAPI.is_registered..sB             r)allr^IAID_PASSWORD_FILErPs r is_registeredz#IndependentAgentIDAPI.is_registereds:  !mS-CD      rcKtrGtd{Vtrtd |jd}|std|S#t$r}td||d}~wwxYw)zWEnsure that iaid token is up to date Return iaid token or raise IAIDTokenError.NzIAID token is expiredascii)encodingzIAID_TOKEN_FILE is emptyzCan't get iaid token, reason: )ris_token_expiredr"rIAID_TOKEN_FILEr`strip Exception)rJtokenes r get_tokenzIndependentAgentIDAPI.get_tokens ! 1 1 3 3 >'--// / / / / / / /$5577 >$%<=== N'1171CCIIKKE A$%?@@@L N N N !E!!E!EFFA M Ns$?B$$ C.CCreturnclK|d{V}d|i}||j|d}||d{V}|d}|t d| |jdi|S#t$r}t d|d||d}~wwxYw) NzX-AuthGET)rerd token_infozwrong response %rzincomplete token_info z: r)rrn TOKEN_INFO async_requestgetr r/ TypeError)rJ iaid_tokenrerequestresultr}r~s r_get_token_infoz%IndependentAgentIDAPI._get_token_infos==??****** Z(,,s~wu,MM((11111111 <(( =.77 7 O 3=))5)) ) O O O(UUUAAFGGQ N Os B B3B..B3c tj|j}|j}n#t$rd}YnwxYwt j|z t kS)Ng)osstatrzst_mtimeFileNotFoundErrortimer)rJrrs rryz&IndependentAgentIDAPI.is_token_expiredsb %73.//D}HH!   HHH y{{X%++s # 22Fr3c `K|j} |j4d{V|rF|r|r dddd{VdS|,||kr dddd{VdSt }t j}|r||d<|j|jfi|} | |d{V}|j dtt|j|d|jd|dtt|j|d|jd |d{Vn#t&$r} t(d | || j| jd ks | jd kr/|t.kr$|d|j|||dz|n"t(d|j|| Yd} ~ dddd{VdSd} ~ wwxYw dddd{VdS#1d{VswxYwYdS#t8$rt(d|YdSwxYw)Nr)T missing_okr#backupuidgid permissionspasswordi)rrz0Something went wrong on register %r - attempt %sr r3rOz-Failed to register (%s) after %s attempts: %rz>$)  ,,..((((((((W NNJ  -=C//=C//!J.. &L!0#aK$+ & K#,#  FFFgC )C )C )C )C )C )C )C )C )C )C )C )C )C )0V)GC )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )C )H    LLN       s~ JI1JI12J;I16F*7B3I1* I4B I>I1JII1J1 I;;J>I;?J%J-,J-cPK|js|d{VdStj}|d{V}|j|dks|j|dkr7t d|| d{VdS|j }|jr|j|ks|jr|d{VdSdS)z!Check whether the agent activatedNstatusidzGot a corrupted token: %r)rr_r!r rrr(rr)rHr reactivater^r`r'r#r*r")rJlicr}r#s rensure_is_activated_and_validz3IndependentAgentIDAPI.ensure_is_activated_and_valid sJ&--// ,,.. F"$$))++++++++  377 $ $   _ - - LL4e < < <.."" " " " " " " " F}&&(({ ejD00E4D0))++         10rc>|jjSr])rtrrrPs rrz)IndependentAgentIDAPI._get_credentials_tss%**,,55rc K|jr|td<dS|s6t d|d{VdStj rL|td<| r| d{VdStj }|st ddS|j 4d{V|jr0|td< dddd{VdS|j}|j}|}||j|||}|td<d} ||d{V|j|jd| d{Vn#t0$r}t d|||jr|jd krd}nr|jrI|jd ks |jd kr3|t4kr(|d |j|d z|t:n"t d|j||Yd}~nd}~wwxYwdddd{Vn#1d{VswxYwY|r|d|d{VdSdS)Nr#z&need to register first before activatez9Can't continue iaid activation: no valid license is found)r#rlicenseFTrz.Something went wrong on activate %r attempt %srrr!r3rBz-Failed to activate (%s) after %s attempts: %rrr) rr_rarrurHrr r is_freeryr"r_activate_lockr^r`rtrrn ACTIVATE_URLrtouchrzrr rrrMr!_ACTIVATE_MINIMUM_TIMEOUTrr) rJr9rr#rcredentials_tsrneed_to_registerr~s rr!zIndependentAgentIDAPI.activate!sc  " ) ) + +  AfI F  ""  NNC D D D,,.. F      AfI##%% "iikk!!!!!!! F"$$  NNK    F%2 "2 "2 "2 "2 "2 "2 "2 "&--// LLNN&  2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 " =**,,D-7799H 4466Nll th#GAfI$ $ "''000000000'--///B#**d*;;;iikk!!!!!!!!C   D =Q]c%9%9'+$$M#--#1E1E*,, MM" ! ' 9 "LLG( 3 #2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "2 "f  P,,T,OO O O O O O O O O O P PsD!7L+A0L4I6L K=B#K83L8K==L LLctK|jd|d{VdS)NTr)rrr!rPs rrz IndependentAgentIDAPI.reactivateksF &&$&777llnnrcK|stddS|j}|j}|}||j||} | |d{V}tt|j |d|j d|ddS#t$r}td|||t"kr]|j |jdkr"|d |j|d z| nW|jd kr|d |d{Vn(td|j||Yd}~dSYd}~dSYd}~dSd}~wwxYw)Nz#need to register first before login)r#rr}rrrz/Something wrong happened on login %r attempt %srr"r3rOrTrz*Failed to login (%s) after %s attempts: %r)rurHrr^r`rtrrn LOGIN_URLrr r.rzr_r[r rrrrMr"r r)rJr9r#rrrrr~s rr"zIndependentAgentIDAPI.loginpsS  ""  LL> ? ? ? F}&&(()33550022,,s}4(,KK ,,W55555555F. C'((w*1133HHJJ!       -    NNA1g   ##=(AMS,@,@MMGaK"]c)),,"' @$  sD GB&G  G)Nrb)FNr3)r3).rrrAPI_PATHrr _BASE_URLformatrrrrrIAID_DIRr^rtrzrrCrrr4Lockrrr/ staticmethodrr; classmethodrMrQrUr[rarnrurrryr rrr!rr"rrrrr*s#H73=(//**E*EFFL73=(//**E*EFFL xw'?'?@@I (E(EFFJt%&&H6!I!O3-O"%55F +*,,N!W\^^NYd        FGH\EF@@@8@@@[@ ;;[;EE[E//\/[     \    [  N N[ N Oi O O O[ O,,[,PPP[Pd[$66[6GPGPGP[GPR[((([(((rr))r4rXrhrr6r dataclassesrloggingrpathlibrtypingr urllib.parserurllib.requestrdefence360agent.api.serverr r !defence360agent.contracts.licenser defence360agent.utilsr r defence360agent.utils.commonr&defence360agent.internals.global_scoper1defence360agent.internals.deadlock_detecting_lockrrrrHrr8r RuntimeErrorrrrrrrs !!!!!! """"""44444444888888BBBBBBBB,,,,,,444444 8    /////\///oooooCooooordefence360agent/internals/__pycache__/lazy_load.cpython-311.opt-1.pyc0000644000000000000000000000107700000000000022401 0ustar r_j GddZdS)ceZdZdZdZdS) CoreSource)z"defence360agent.contracts.messages)zdefence360agent.simple_rpcz0defence360agent.feature_management.rpc.endpointsN)__name__ __module__ __qualname__MESSAGES ENDPOINTSX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/lazy_load.pyrrs6HIIIr rN)rr r r r s7r defence360agent/internals/__pycache__/lazy_load.cpython-311.pyc0000644000000000000000000000107700000000000021442 0ustar r_j GddZdS)ceZdZdZdZdS) CoreSource)z"defence360agent.contracts.messages)zdefence360agent.simple_rpcz0defence360agent.feature_management.rpc.endpointsN)__name__ __module__ __qualname__MESSAGES ENDPOINTSX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/lazy_load.pyrrs6HIIIr rN)rr r r r s7r defence360agent/internals/__pycache__/logger.cpython-311.opt-1.pyc0000644000000000000000000005142600000000000021705 0ustar r_j">ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z m Z ddl m Z ddlZddlZddlmZmZddlmZddlmZddlmZddlmZmZdd lmZejd d Zej e!Z"d#d Z#GddZ$e ddZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-de.fdZ/dZ0dZ1e de.fdZ2e de.fd Z3Gd!d"Z4dS)$N)contextmanagersuppress) lru_cache)configsentry) AcronisBackup)Logger)Sentry)antivirus_mode is_root_user)tagsIMUNIFY360_LOGGING_PREFIXFc tj}n#ttf$rd}YnwxYw|rt jtj|tjj dt j 5}tj D]\}}|||dtjdi|_dddn #1swxYwYdddSd d dS) NTon)dsndebugreleaseattach_stacktraceid server_idERRORz-sentry_sdk.integrations.logging.SentryHandler)levelclassNOTSETzlogging.NullHandler)r ENABLEKeyErrorAssertionError sentry_sdkinitDSNrCoreVERSIONconfigure_scoperr itemsset_tagtaguser)rerror_reportingscoper'values U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logger.py _sentry_initr-sR - n %  K'"      ' ) ) 9U$kmm1133 * * U c5)))) ; 7 78EJ 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 D   *   s %%.ACCCcHeZdZdejjzZedZdZ dS)_LoggerDynConfigz /var/log/%scxdtjjdtjpt jS)Nz /var/log/z _user_logs/)rr"PRODUCTgetpassgetuserosgetuidr, _user_log_dirz_LoggerDynConfig._user_log_dir:s7 K    O   , ,  r7c,t}|r|jn||_dgddgddgdddt ddd|jzd d d ddd |jzd d d ddd |jzd d d ddd|jzd d d ddd|jzd d d dddddddd|jzd d dddgdddddt diddt didd id!d"d#|_dgd|jd$d%<ddtj |jtj d d d|jd&d'<|s^|jd& D]@}| d(d kr#d)|d(<tj|d*<tj|d+<?dSdS),NDEBUG)rhandlersINFO)network"defence360agent.internals.the_sink event_hookWARNING abstimestampz %s/error.logzlogging.FileHandlerutf8)r formatterfilenamerencodingz%s/network.logz %s/debug.logz%s/console.log eventhookz %s/hook.logzlogging.StreamHandlerzext://sys.stderr)rDrstreamr reltimestampz%s/process_message.log)rDrrErrF)r error_log network_log debug_log console_loghook_logconsoleprocess_message_logr)rMrJrlogsformatz*%(levelname)-7s [+%(relativeCreated)5dms] z%(name)50s|%(message)sz%(levelname)-7s [%(asctime)s] z%(name)s: %(message)sz%(created)d : %(message)s)rIrBrGF)loggersversionr;rootmkdir formattersdisable_existing_loggersrSAcronisClientInstallerr;acronis_installer_logrz$logging.handlers.RotatingFileHandlermaxBytes backupCount)r _ROOT_LOG_DIRr8log_dirr-PREFIXmutableDictConfigr4pathjoinrLOG_NAMEvaluesgetConfigMAX_LOG_FILE_SIZE BACKUP_COUNT)selfis_roothandlers r,__init__z_LoggerDynConfig.__init__As..-4Nt))$:L:L:N:N % " % "77 $ " &..&!/ . =2 & %!/ 04< ?2 &   %!/ . =2 & $!/ 04< ?2 &   $!, - <2 & "040# "0 % 84< G2 & ( (W77r":!:::!''''! '(CD).Gd" d" NG G y)*BC ( T\=3IJJ*G G z*+BC A  1*=DDFF A A;;w''+@@@'MGG$*0*BGJ'-3-@GM* A A  A Ar7N) __name__ __module__ __qualname__rr"r1r] staticmethodr8rlr6r7r,r/r/7sU!FK$77M  \ BABABABABAr7r/r@ctSN)r/r6r7r, _late_initrss   r7ctt5tjdcdddS#1swxYwYdS)ay :return bool: True if python interpreter is being run in CageFS container, otherwise False :raise: never Current implementation simply checks "/var/.cagefs" presence, as Anton Volkov consulted us to do. Placing this function not in 'subsys' package, because 'logger' module is one of cornerstones dependency for 'subsys' package as well. z /var/.cagefsN)rOSErrorr4raexistsr6r7r,_we_are_in_cagefsrws '  ..w~~n--..................sAAAcd}|||tj|D]d\}}}|D],}|tj|||-|D],}|tj|||-edS)zChange file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm Permission errors are logged to stderr and are ignored in any case. c tj||dS#t$r>}tjd||Yd}~dSd}~wwxYw)Nz [WARNING] cannot chmod on {}: {})r4chmodPermissionErrorsysstderrwriterR) file_dir_path permissiones r, _os_chmodz"_chmod_log_dirs.._os_chmods  H]J / / / / /    J  299-KK          s A!3AA!N)r4walkrarb) dirnamedir_perm file_permrradirsfiles directorynames r,_chmod_log_dirsrsIgx   WW--;;dE ? ?I Ibgll433X > > > > ; ;D Ibgll4.. : : : : ;;;r7c*tjdrdS tjt j}tj|tjdt|tjtj tj t jtt _dS#t$$ret'sSt)jt jt jdtjjzYdSYdSt4$rTt)jt jt jdtjjzYdSwxYw)z> Re-catch with _LoggerDynConfig and re-open log files IMUNIFY360_DISABLE_LOGGINGTexist_ok)filez%s logger is not available. N)r4getenvr cached_fillrsr^makedirsrf LOG_DIR_PERMr LOG_FILE_PERMloggingr dictConfigr`_log_uncaught_exceptionsr| excepthookrurw traceback print_excr}r~r"r1 Exception)r^s r, reconfigurers y-..6  6      ll*G K!4t D D D D GV%8&:N O O O N % %jll&D E E E,6CNNN+   %&& #4444   3fk6II         SZ 0 0 0 0 J  /&+2EE       sBCA(F5AFFct|trtj|||dStd|||fdS)Nzuncaught exception)exc_info) issubclassKeyboardInterruptr|__excepthook__loggercritical)exc_type exc_value exc_tracebacks r,rrs`(-.. 8Y >>> OO)]'Kr7ct|5}tj|}dddn #1swxYwYtj|t dSrr)openyaml safe_loadrsr`updater)rE config_filers r,update_logging_config_from_filer$s h-; ,,---------------LL"))&111MMMMMs 155ctjj}tjdD].}|tj|j/d|DS)NrScg|]C}t|dr1t|jdr|jtjk<|jDS)rHfileno)hasattrrHr|r}).0hs r, zget_fds..1s_    1h    AHh ' '   H " "  # " "r7)rrUr;rsr`keysextend getLogger)r;_loggers r,get_fdsr,sw|$H<<1)<AACC==)'22;<<<<     r7cldtjdDS)Nc,g|]\}}d|v |dS)rEr6)r_rds r,rz&get_log_file_names..;s6    Av    z   r7r;)rsr`r%r6r7r,get_log_file_namesr:s;  #7 CIIKK   r7c|tjvr,tjdtj|jzStjd|zS)Nznetwork.)r|modulesrrrm)rs r,getNetworkLoggerrBsE s{ ck$.?.H!HIII d!2333r7returnc(tjS)z| Return base log directory for the product. Supposed to be used by clients to build the path to their own logs. )rsr^r6r7r,r^r^Ms << r7ctjr8tjdddd|dkr8tjdddd|dkr8tjdd dd |d kr2tjd dd tjddddt dS)NrSrYr;rZr=rKr>rPrUrLr?rN)r disabledrsr`appendr)verboses r, setLogLevelrUs* &y12JK  &( ) ) )!|| &y1)<  &   !|| &y1 0  f2333!|| &v.z:AA+NNNLL"9-l;JGNNMMMMMr7cf|tjddd<tdS)z' also results in reconfigure() r;rOrN)rsr`r) newloglevels r,setConsoleLogLevelrls4 LL":.y9MMMMMr7scan_idc#JKtjtjd}t |d5}|tjdd|d|V|dddddS#1swxYwYdS)Nzaibolit_actions.loga%Y-%m-%d %H:%M:%S |  ) r4rarbrsr^rr~timestrftime)rrafs r,openAibolitActionsLogrxs 7<< ,.C D DD dCA 4=!455FF'FFFGGG sABBBc#zKtj}tj|dtj|d}t |d5}|tj dd|d|V|dddddS#1swxYwYdS)NTrzmds_actions.logrrrr) rsr^r4rrarbrr~rr)rr^rars r,openMdsActionsLogrsll"GK$'''' 7<<!2 3 3D dCA 4=!455FF'FFFGGG sAB00B47B4c4eZdZGddZdZdZdS)EventHookLoggercBeZdZGddZdZd dZdZdZdS) EventHookLogger._EventLoggerc8eZdZdZdZdZdZd dZdZdZ d S) (EventHookLogger._EventLogger._HookLoggerzD{uuid:s} : {action:s} {native:s}: {event:s} : {subtype:s} : {path:s}c||_|j|_|j|_|j|_|j|_||_dSrr)raeventsubtypeuuidlognative)riparentrars r,rlz1EventHookLogger._EventLogger._HookLogger.__init__s8  #\ %~ "K !:$ r7c|Srrr6ris r, __enter__z2EventHookLogger._EventLogger._HookLogger.__enter__s r7cdSrrr6rirexc_valexc_tbs r,__exit__z1EventHookLogger._EventLogger._HookLogger.__exit__sr7rct|j||jrdnd|j|j|jd}|jjdi|}|rd||g}| |dS)Nznative r)ractionrrrraz : r6) strrrrrratplrRrbr)rirmessagedatamsgs r,_logz-EventHookLogger._EventLogger._HookLogger._logs NN$+/;>iiB!Z#| I &dho----5**c7^44C r7c0|ddS)Nstarted)rrs r,beginz.EventHookLogger._EventLogger._HookLogger.begins )$$$$$r7c|dkrdnd}|r$d|t|g}|rBt|tr|d}d||g}|d|dS) NrOKr:backslashreplace)errors done)rbr isinstancebytesdecoder)ri exit_codeerrrs r,finishz/EventHookLogger._EventLogger._HookLogger.finishs"+q..$$gB!hhY'@AAG8!#u--D!jj0BjCC"ii#77G &'*****r7N)r) rmrnrorrlrrrrrr6r7r, _HookLoggerrs}5   % % %            % % % + + + + +r7rcj||_||_tj|_|j|_dSrr)rrruuid4r)rirrrs r,rlz%EventHookLogger._EventLogger.__init__s*DJ"DL DIzDHHHr7Fc2||||S)N)r)r)rirars r,__call__z%EventHookLogger._EventLogger.__call__s##D$v#>> >r7c|Srrr6rs r,rz&EventHookLogger._EventLogger.__enter__sKr7cdSrrr6rs r,rz%EventHookLogger._EventLogger.__exit__s Dr7NF)rmrnrorrlr rrr6r7r, _EventLoggerrs~0 +0 +0 +0 +0 +0 +0 +0 +d " " "  ? ? ? ?        r7rcFtjd}|j|_dS)Nr?)rrinfor)rirs r,rlzEventHookLogger.__init__s"<00;r7c0||||Srr)r)rirrs r,r zEventHookLogger.__call__s  ug666r7N)rmrnrorrlr r6r7r,rrsc@@@@@@@@D77777r7rr)5r2rlogging.configlogging.handlersr4r|rrr contextlibrr functoolsrrrdefence360agent.contractsrr defence360agent.contracts.configrr rfr defence360agent.utilsr r defence360agent.applicationr environrer_rrmrr-r/rsrwrrrrrrrrr^rrrrrr6r7r,rs  //////// 44444444::::::======333333>>>>>>>>,,,,,, 3R 8 8  8 $ $    :LALALALALALALALA^ 1 . . . ;;;2#6#6#6L   444     .3sH7H7H7H7H7H7H7H7H7H7r7defence360agent/internals/__pycache__/logger.cpython-311.pyc0000644000000000000000000005142600000000000020746 0ustar r_j">ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z m Z ddl m Z ddlZddlZddlmZmZddlmZddlmZddlmZddlmZmZdd lmZejd d Zej e!Z"d#d Z#GddZ$e ddZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-de.fdZ/dZ0dZ1e de.fdZ2e de.fd Z3Gd!d"Z4dS)$N)contextmanagersuppress) lru_cache)configsentry) AcronisBackup)Logger)Sentry)antivirus_mode is_root_user)tagsIMUNIFY360_LOGGING_PREFIXFc tj}n#ttf$rd}YnwxYw|rt jtj|tjj dt j 5}tj D]\}}|||dtjdi|_dddn #1swxYwYdddSd d dS) NTon)dsndebugreleaseattach_stacktraceid server_idERRORz-sentry_sdk.integrations.logging.SentryHandler)levelclassNOTSETzlogging.NullHandler)r ENABLEKeyErrorAssertionError sentry_sdkinitDSNrCoreVERSIONconfigure_scoperr itemsset_tagtaguser)rerror_reportingscoper'values U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logger.py _sentry_initr-sR - n %  K'"      ' ) ) 9U$kmm1133 * * U c5)))) ; 7 78EJ 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 D   *   s %%.ACCCcHeZdZdejjzZedZdZ dS)_LoggerDynConfigz /var/log/%scxdtjjdtjpt jS)Nz /var/log/z _user_logs/)rr"PRODUCTgetpassgetuserosgetuidr, _user_log_dirz_LoggerDynConfig._user_log_dir:s7 K    O   , ,  r7c,t}|r|jn||_dgddgddgdddt ddd|jzd d d ddd |jzd d d ddd |jzd d d ddd|jzd d d ddd|jzd d d dddddddd|jzd d dddgdddddt diddt didd id!d"d#|_dgd|jd$d%<ddtj |jtj d d d|jd&d'<|s^|jd& D]@}| d(d kr#d)|d(<tj|d*<tj|d+<?dSdS),NDEBUG)rhandlersINFO)network"defence360agent.internals.the_sink event_hookWARNING abstimestampz %s/error.logzlogging.FileHandlerutf8)r formatterfilenamerencodingz%s/network.logz %s/debug.logz%s/console.log eventhookz %s/hook.logzlogging.StreamHandlerzext://sys.stderr)rDrstreamr reltimestampz%s/process_message.log)rDrrErrF)r error_log network_log debug_log console_loghook_logconsoleprocess_message_logr)rMrJrlogsformatz*%(levelname)-7s [+%(relativeCreated)5dms] z%(name)50s|%(message)sz%(levelname)-7s [%(asctime)s] z%(name)s: %(message)sz%(created)d : %(message)s)rIrBrGF)loggersversionr;rootmkdir formattersdisable_existing_loggersrSAcronisClientInstallerr;acronis_installer_logrz$logging.handlers.RotatingFileHandlermaxBytes backupCount)r _ROOT_LOG_DIRr8log_dirr-PREFIXmutableDictConfigr4pathjoinrLOG_NAMEvaluesgetConfigMAX_LOG_FILE_SIZE BACKUP_COUNT)selfis_roothandlers r,__init__z_LoggerDynConfig.__init__As..-4Nt))$:L:L:N:N % " % "77 $ " &..&!/ . =2 & %!/ 04< ?2 &   %!/ . =2 & $!/ 04< ?2 &   $!, - <2 & "040# "0 % 84< G2 & ( (W77r":!:::!''''! '(CD).Gd" d" NG G y)*BC ( T\=3IJJ*G G z*+BC A  1*=DDFF A A;;w''+@@@'MGG$*0*BGJ'-3-@GM* A A  A Ar7N) __name__ __module__ __qualname__rr"r1r] staticmethodr8rlr6r7r,r/r/7sU!FK$77M  \ BABABABABAr7r/r@ctSN)r/r6r7r, _late_initrss   r7ctt5tjdcdddS#1swxYwYdS)ay :return bool: True if python interpreter is being run in CageFS container, otherwise False :raise: never Current implementation simply checks "/var/.cagefs" presence, as Anton Volkov consulted us to do. Placing this function not in 'subsys' package, because 'logger' module is one of cornerstones dependency for 'subsys' package as well. z /var/.cagefsN)rOSErrorr4raexistsr6r7r,_we_are_in_cagefsrws '  ..w~~n--..................sAAAcd}|||tj|D]d\}}}|D],}|tj|||-|D],}|tj|||-edS)zChange file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm Permission errors are logged to stderr and are ignored in any case. c tj||dS#t$r>}tjd||Yd}~dSd}~wwxYw)Nz [WARNING] cannot chmod on {}: {})r4chmodPermissionErrorsysstderrwriterR) file_dir_path permissiones r, _os_chmodz"_chmod_log_dirs.._os_chmods  H]J / / / / /    J  299-KK          s A!3AA!N)r4walkrarb) dirnamedir_perm file_permrradirsfiles directorynames r,_chmod_log_dirsrsIgx   WW--;;dE ? ?I Ibgll433X > > > > ; ;D Ibgll4.. : : : : ;;;r7c*tjdrdS tjt j}tj|tjdt|tjtj tj t jtt _dS#t$$ret'sSt)jt jt jdtjjzYdSYdSt4$rTt)jt jt jdtjjzYdSwxYw)z> Re-catch with _LoggerDynConfig and re-open log files IMUNIFY360_DISABLE_LOGGINGTexist_ok)filez%s logger is not available. N)r4getenvr cached_fillrsr^makedirsrf LOG_DIR_PERMr LOG_FILE_PERMloggingr dictConfigr`_log_uncaught_exceptionsr| excepthookrurw traceback print_excr}r~r"r1 Exception)r^s r, reconfigurers y-..6  6      ll*G K!4t D D D D GV%8&:N O O O N % %jll&D E E E,6CNNN+   %&& #4444   3fk6II         SZ 0 0 0 0 J  /&+2EE       sBCA(F5AFFct|trtj|||dStd|||fdS)Nzuncaught exception)exc_info) issubclassKeyboardInterruptr|__excepthook__loggercritical)exc_type exc_value exc_tracebacks r,rrs`(-.. 8Y >>> OO)]'Kr7ct|5}tj|}dddn #1swxYwYtj|t dSrr)openyaml safe_loadrsr`updater)rE config_filers r,update_logging_config_from_filer$s h-; ,,---------------LL"))&111MMMMMs 155ctjj}tjdD].}|tj|j/d|DS)NrScg|]C}t|dr1t|jdr|jtjk<|jDS)rHfileno)hasattrrHr|r}).0hs r, zget_fds..1s_    1h    AHh ' '   H " "  # " "r7)rrUr;rsr`keysextend getLogger)r;_loggers r,get_fdsr,sw|$H<<1)<AACC==)'22;<<<<     r7cldtjdDS)Nc,g|]\}}d|v |dS)rEr6)r_rds r,rz&get_log_file_names..;s6    Av    z   r7r;)rsr`r%r6r7r,get_log_file_namesr:s;  #7 CIIKK   r7c|tjvr,tjdtj|jzStjd|zS)Nznetwork.)r|modulesrrrm)rs r,getNetworkLoggerrBsE s{ ck$.?.H!HIII d!2333r7returnc(tjS)z| Return base log directory for the product. Supposed to be used by clients to build the path to their own logs. )rsr^r6r7r,r^r^Ms << r7ctjr8tjdddd|dkr8tjdddd|dkr8tjdd dd |d kr2tjd dd tjddddt dS)NrSrYr;rZr=rKr>rPrUrLr?rN)r disabledrsr`appendr)verboses r, setLogLevelrUs* &y12JK  &( ) ) )!|| &y1)<  &   !|| &y1 0  f2333!|| &v.z:AA+NNNLL"9-l;JGNNMMMMMr7cf|tjddd<tdS)z' also results in reconfigure() r;rOrN)rsr`r) newloglevels r,setConsoleLogLevelrls4 LL":.y9MMMMMr7scan_idc#JKtjtjd}t |d5}|tjdd|d|V|dddddS#1swxYwYdS)Nzaibolit_actions.loga%Y-%m-%d %H:%M:%S |  ) r4rarbrsr^rr~timestrftime)rrafs r,openAibolitActionsLogrxs 7<< ,.C D DD dCA 4=!455FF'FFFGGG sABBBc#zKtj}tj|dtj|d}t |d5}|tj dd|d|V|dddddS#1swxYwYdS)NTrzmds_actions.logrrrr) rsr^r4rrarbrr~rr)rr^rars r,openMdsActionsLogrsll"GK$'''' 7<<!2 3 3D dCA 4=!455FF'FFFGGG sAB00B47B4c4eZdZGddZdZdZdS)EventHookLoggercBeZdZGddZdZd dZdZdZdS) EventHookLogger._EventLoggerc8eZdZdZdZdZdZd dZdZdZ d S) (EventHookLogger._EventLogger._HookLoggerzD{uuid:s} : {action:s} {native:s}: {event:s} : {subtype:s} : {path:s}c||_|j|_|j|_|j|_|j|_||_dSrr)raeventsubtypeuuidlognative)riparentrars r,rlz1EventHookLogger._EventLogger._HookLogger.__init__s8  #\ %~ "K !:$ r7c|Srrr6ris r, __enter__z2EventHookLogger._EventLogger._HookLogger.__enter__s r7cdSrrr6rirexc_valexc_tbs r,__exit__z1EventHookLogger._EventLogger._HookLogger.__exit__sr7rct|j||jrdnd|j|j|jd}|jjdi|}|rd||g}| |dS)Nznative r)ractionrrrraz : r6) strrrrrratplrRrbr)rirmessagedatamsgs r,_logz-EventHookLogger._EventLogger._HookLogger._logs NN$+/;>iiB!Z#| I &dho----5**c7^44C r7c0|ddS)Nstarted)rrs r,beginz.EventHookLogger._EventLogger._HookLogger.begins )$$$$$r7c|dkrdnd}|r$d|t|g}|rBt|tr|d}d||g}|d|dS) NrOKr:backslashreplace)errors done)rbr isinstancebytesdecoder)ri exit_codeerrrs r,finishz/EventHookLogger._EventLogger._HookLogger.finishs"+q..$$gB!hhY'@AAG8!#u--D!jj0BjCC"ii#77G &'*****r7N)r) rmrnrorrlrrrrrr6r7r, _HookLoggerrs}5   % % %            % % % + + + + +r7rcj||_||_tj|_|j|_dSrr)rrruuid4r)rirrrs r,rlz%EventHookLogger._EventLogger.__init__s*DJ"DL DIzDHHHr7Fc2||||S)N)r)r)rirars r,__call__z%EventHookLogger._EventLogger.__call__s##D$v#>> >r7c|Srrr6rs r,rz&EventHookLogger._EventLogger.__enter__sKr7cdSrrr6rs r,rz%EventHookLogger._EventLogger.__exit__s Dr7NF)rmrnrorrlr rrr6r7r, _EventLoggerrs~0 +0 +0 +0 +0 +0 +0 +0 +d " " "  ? ? ? ?        r7rcFtjd}|j|_dS)Nr?)rrinfor)rirs r,rlzEventHookLogger.__init__s"<00;r7c0||||Srr)r)rirrs r,r zEventHookLogger.__call__s  ug666r7N)rmrnrorrlr r6r7r,rrsc@@@@@@@@D77777r7rr)5r2rlogging.configlogging.handlersr4r|rrr contextlibrr functoolsrrrdefence360agent.contractsrr defence360agent.contracts.configrr rfr defence360agent.utilsr r defence360agent.applicationr environrer_rrmrr-r/rsrwrrrrrrrrr^rrrrrr6r7r,rs  //////// 44444444::::::======333333>>>>>>>>,,,,,, 3R 8 8  8 $ $    :LALALALALALALALA^ 1 . . . ;;;2#6#6#6L   444     .3sH7H7H7H7H7H7H7H7H7H7r7defence360agent/internals/__pycache__/logging_protocol.cpython-311.opt-1.pyc0000644000000000000000000000710400000000000023767 0ustar r_jE4ddlZGddejZdS)Nc2eZdZdZdZdZdZdZdZdS)LoggingProtocolc0||_||_||_dSN)_logger_network_logger_real_protocol)selfloggernetwork_logger real_protocols _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logging_protocol.py__init__zLoggingProtocol.__init__s -+cpjdfddS)NzConnection made.c8jSr)r connection_mader transportsrz1LoggingProtocol.connection_made.. sT0@@KKrrdebug_handlers``rrzLoggingProtocol.connection_made sA ""#5666 KKKKKLLLLLrcpjdfddS)NzConnection lost.c8jSr)r connection_lost)excr srrz1LoggingProtocol.connection_lost..sT0@@EErr)r rs``rrzLoggingProtocol.connection_lostsA ""#5666 EEEEEFFFFFrcjdfddS)Nzdatagram_received: {!r}c:jSr)r datagram_received)addrdatar srrz3LoggingProtocol.datagram_received..sT0BB4NNrrrformatr)r r"r!s```rr z!LoggingProtocol.datagram_receivedsS ""#<#C#CD#I#IJJJ NNNNNNOOOOOrcjdfddS)Nzdata_received: {!r}c8jSr)r data_received)r"r srrz/LoggingProtocol.data_received..sT0>>tDDrr#)r r"s``rr'zLoggingProtocol.data_receivedsO ""#8#?#?#E#EFFF DDDDDEEEEErc |dS#t$r2}|jt|Yd}~dSd}~wwxYwr) Exceptionr exceptionstr)r imples rrzLoggingProtocol._handlesc + DFFFFF + + + L " "3q66 * * * * * * * * * +s  A 'AA N) __name__ __module__ __qualname__rrrr r'rrrrrsz,,, MMMGGGPPPFFF+++++rr)asyncioProtocolrr1rrr4sE+++++g&+++++rdefence360agent/internals/__pycache__/logging_protocol.cpython-311.pyc0000644000000000000000000000710400000000000023030 0ustar r_jE4ddlZGddejZdS)Nc2eZdZdZdZdZdZdZdZdS)LoggingProtocolc0||_||_||_dSN)_logger_network_logger_real_protocol)selfloggernetwork_logger real_protocols _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/logging_protocol.py__init__zLoggingProtocol.__init__s -+cpjdfddS)NzConnection made.c8jSr)r connection_mader transportsrz1LoggingProtocol.connection_made.. sT0@@KKrrdebug_handlers``rrzLoggingProtocol.connection_made sA ""#5666 KKKKKLLLLLrcpjdfddS)NzConnection lost.c8jSr)r connection_lost)excr srrz1LoggingProtocol.connection_lost..sT0@@EErr)r rs``rrzLoggingProtocol.connection_lostsA ""#5666 EEEEEFFFFFrcjdfddS)Nzdatagram_received: {!r}c:jSr)r datagram_received)addrdatar srrz3LoggingProtocol.datagram_received..sT0BB4NNrrrformatr)r r"r!s```rr z!LoggingProtocol.datagram_receivedsS ""#<#C#CD#I#IJJJ NNNNNNOOOOOrcjdfddS)Nzdata_received: {!r}c8jSr)r data_received)r"r srrz/LoggingProtocol.data_received..sT0>>tDDrr#)r r"s``rr'zLoggingProtocol.data_receivedsO ""#8#?#?#E#EFFF DDDDDEEEEErc |dS#t$r2}|jt|Yd}~dSd}~wwxYwr) Exceptionr exceptionstr)r imples rrzLoggingProtocol._handlesc + DFFFFF + + + L " "3q66 * * * * * * * * * +s  A 'AA N) __name__ __module__ __qualname__rrrr r'rrrrrsz,,, MMMGGGPPPFFF+++++rr)asyncioProtocolrr1rrr4sE+++++g&+++++rdefence360agent/internals/__pycache__/message_status_publisher.cpython-311.opt-1.pyc0000644000000000000000000002460400000000000025530 0ustar r_jRdZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl mZmZejeZdZejddZeddzZejd d Zd Zd Zd ZGddZdefdZGddZ e Z!ej"e!j#eZ$dS)u Lightweight message status publisher for asyncclient. Publishes MESSAGE_STATUS events to the local proxy which relays them to the EMQX broker via MQTT. Each call to report() submits an HTTP POST to a thread pool — no batching or internal queue. Usage:: publisher = MessageStatusPublisher() message_id_gen = Gen() msg = {...} message_id_gen.enrich(msg) # adds message_reporter_id / message_reporter_increment publisher.report(msg, reporter_id_gen) N)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabledz/var/imunify360/iaidIMUNIFY_PROXY_URLzhttp://127.0.0.1:11234/z/api/v1/mqtt-publishIMUNIFY_PROXY_API_KEYc6eZdZdZddZdefdZdeddfdZdS) Genz_ID + monotonic counter generator. Each instance has its own UUID and its own counter. returnNc~tjj|_d|_t j|_dS)Nr)uuiduuid4hexid_counter threadingLock_lockselfs g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/message_status_publisher.py__init__z Gen.__init__=s,*,," ^%% cv|j5|j}|xjdz c_|cdddS#1swxYwYdS)N)rr)rvalues r_nextz Gen._nextBs Z  ME MMQ MM                  s .22msgcH|j|d<||d<dS)z>Add message_reporter_id and message_reporter_increment to msg.message_reporter_idmessage_reporter_incrementN)rr )rr!s renrichz Gen.enrichHs(%)W !",0JJLL ()))rrN) __name__ __module__ __qualname____doc__rintr dictr%rrr r 7so &&&& s 9$94999999rr rc tt5}|cdddS#1swxYwYdS#t$rYdSwxYw)Nr)open _IAID_PATHreadstripOSError)fs r _read_iaidr5Ns *   $6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ rrs3A&A  A A  AA A A$#A$cneZdZddZdefdZdefdZddZdede d e ddfd Z d ed e d e ddfd Z ddZ dS)MessageStatusPublisherrNc"d|_tj|_d|_t jtd|_ tj t|_ d|_ tj|_dS)NrFz msg-status) max_workersthread_name_prefixr)_iaidrr _init_lock _initialized concurrentfuturesThreadPoolExecutor _MAX_WORKERS_poolBoundedSemaphore _MAX_INFLIGHT _inflight_dropped _dropped_lockrs rrzMessageStatusPublisher.__init__Wsv #.**!'::$+;   #3MBB &^--rcf|j5|jdc}|_|cdddS#1swxYwYdS)z:Drops since the last call, then reset (delta for metrics).rN)rGrF)rdroppeds r pop_droppedz"MessageStatusPublisher.pop_droppedcs    %)]A "GT]                  s &**cJ|j}td|j|jz S)z6In-flight reports awaiting completion (gauge, 0..cap).r)rEmax_initial_value_value)rsems r queue_depthz"MessageStatusPublisher.queue_depthis#n1c(3:5666rc |jrdS|j5|jr ddddSt|_|js.tdt  ddddSd|_ddddS#1swxYwYdS)NzImsg-status: iaid not available yet (file %s missing or empty), will retryT)r=r<r5r;loggerinfor0rs r_ensure_initializedz*MessageStatusPublisher._ensure_initializedns     F _ % %   % % % % % % % %$DJ:  *  % % % % % % % %!%D  % % % % % % % % % % % % % % % % % %s B;B/BB Br! reporter_genstagectdsdS|dd}|dsdSjdsattr/j5xjdz c_dddn #1swxYwYtd ||dStj |j | |dd|d d ||d } j j|||}n*#t$rjYdSwxYw|fd dS)z3Publish a status record via HTTP POST to the proxy. mqtt_trackingNmethodrr#F)blockingrz3msg-status: queue full, dropping stage=%s method=%sr$r) timestamp reporter_idreporter_incrementr#r$ message_typerVc6jS)N)rErelease)_rs rz/MessageStatusPublisher.report..s4>+A+A+C+Cr)rgetrEacquirerrGrFrRwarningtimerr rBsubmit_do_post RuntimeErrorr`add_done_callback)rr!rUrVrYrecordfutures` rreportzMessageStatusPublisher.report~s /**  F2&&ww,--  F ~%%u%55 9:: ''''MMQ&MM''''''''''''''' NNE    F'?"."4"4"6"6#&77+@"#E#E*-'',a++#    Z&&t}feVLLFF    N " " $ $ $ FF    !C!C!C!CDDDDDs$6BBB"D::#E! E!rkrYc,||jsdS|j|d< tj|}ddi}t r t |d<t jt||d}t j |t5}| ddddS#1swxYwYdS#t$r(}td|||Yd}~dSd}~wwxYw) Niaidz Content-Typezapplication/jsonz X-API-KeyPOST)dataheadersrY)timeoutz.msg-status: POST failed stage=%s method=%s: %r)rTr;jsondumpsencode_PROXY_API_KEYurllibrequestRequest_PUBLISH_ENDPOINTurlopen _POST_TIMEOUTr1 ExceptionrRre) rrkrVrYpayloadrrreqrespes rrhzMessageStatusPublisher._do_posts}   """z  Fv j((//11G%'9:G 6'5 $.((! )C '']'CC t                       NN@           s<B C!2C C!CC!CC!! D+DDc<|jddS)NF)wait)rBshutdownrs rrzMessageStatusPublisher.shutdowns! '''''rr&)r'r(r)rr+rJrPrTr,r strrmrhrr-rrr7r7Vs . . . .S 7S7777 %%%% *E$*Ec*E#*E$*E*E*E*EXtC4((((((rr7)%r*atexitconcurrent.futuresr>rtloggingosrrf urllib.errorrxurllib.requestr'defence360agent.internals.feature_flagsrr getLoggerr'rRr0environrc _PROXY_URLrstripr{rwr}rArDr rr5r7 publisherregisterrmessage_id_genr-rrrs(       8 $ $ # Z^^/1I J J %%c**-CC  7<<   99999999.Co(o(o(o(o(o(o(o(d # " $ $  "###rdefence360agent/internals/__pycache__/message_status_publisher.cpython-311.pyc0000644000000000000000000002460400000000000024571 0ustar r_jRdZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl mZmZejeZdZejddZeddzZejd d Zd Zd Zd ZGddZdefdZGddZ e Z!ej"e!j#eZ$dS)u Lightweight message status publisher for asyncclient. Publishes MESSAGE_STATUS events to the local proxy which relays them to the EMQX broker via MQTT. Each call to report() submits an HTTP POST to a thread pool — no batching or internal queue. Usage:: publisher = MessageStatusPublisher() message_id_gen = Gen() msg = {...} message_id_gen.enrich(msg) # adds message_reporter_id / message_reporter_increment publisher.report(msg, reporter_id_gen) N)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabledz/var/imunify360/iaidIMUNIFY_PROXY_URLzhttp://127.0.0.1:11234/z/api/v1/mqtt-publishIMUNIFY_PROXY_API_KEYc6eZdZdZddZdefdZdeddfdZdS) Genz_ID + monotonic counter generator. Each instance has its own UUID and its own counter. returnNc~tjj|_d|_t j|_dS)Nr)uuiduuid4hexid_counter threadingLock_lockselfs g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/message_status_publisher.py__init__z Gen.__init__=s,*,," ^%% cv|j5|j}|xjdz c_|cdddS#1swxYwYdS)N)rr)rvalues r_nextz Gen._nextBs Z  ME MMQ MM                  s .22msgcH|j|d<||d<dS)z>Add message_reporter_id and message_reporter_increment to msg.message_reporter_idmessage_reporter_incrementN)rr )rr!s renrichz Gen.enrichHs(%)W !",0JJLL ()))rrN) __name__ __module__ __qualname____doc__rintr dictr%rrr r 7so &&&& s 9$94999999rr rc tt5}|cdddS#1swxYwYdS#t$rYdSwxYw)Nr)open _IAID_PATHreadstripOSError)fs r _read_iaidr5Ns *   $6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ rrs3A&A  A A  AA A A$#A$cneZdZddZdefdZdefdZddZdede d e ddfd Z d ed e d e ddfd Z ddZ dS)MessageStatusPublisherrNc"d|_tj|_d|_t jtd|_ tj t|_ d|_ tj|_dS)NrFz msg-status) max_workersthread_name_prefixr)_iaidrr _init_lock _initialized concurrentfuturesThreadPoolExecutor _MAX_WORKERS_poolBoundedSemaphore _MAX_INFLIGHT _inflight_dropped _dropped_lockrs rrzMessageStatusPublisher.__init__Wsv #.**!'::$+;   #3MBB &^--rcf|j5|jdc}|_|cdddS#1swxYwYdS)z:Drops since the last call, then reset (delta for metrics).rN)rGrF)rdroppeds r pop_droppedz"MessageStatusPublisher.pop_droppedcs    %)]A "GT]                  s &**cJ|j}td|j|jz S)z6In-flight reports awaiting completion (gauge, 0..cap).r)rEmax_initial_value_value)rsems r queue_depthz"MessageStatusPublisher.queue_depthis#n1c(3:5666rc |jrdS|j5|jr ddddSt|_|js.tdt  ddddSd|_ddddS#1swxYwYdS)NzImsg-status: iaid not available yet (file %s missing or empty), will retryT)r=r<r5r;loggerinfor0rs r_ensure_initializedz*MessageStatusPublisher._ensure_initializedns     F _ % %   % % % % % % % %$DJ:  *  % % % % % % % %!%D  % % % % % % % % % % % % % % % % % %s B;B/BB Br! reporter_genstagectdsdS|dd}|dsdSjdsattr/j5xjdz c_dddn #1swxYwYtd ||dStj |j | |dd|d d ||d } j j|||}n*#t$rjYdSwxYw|fd dS)z3Publish a status record via HTTP POST to the proxy. mqtt_trackingNmethodrr#F)blockingrz3msg-status: queue full, dropping stage=%s method=%sr$r) timestamp reporter_idreporter_incrementr#r$ message_typerVc6jS)N)rErelease)_rs rz/MessageStatusPublisher.report..s4>+A+A+C+Cr)rgetrEacquirerrGrFrRwarningtimerr rBsubmit_do_post RuntimeErrorr`add_done_callback)rr!rUrVrYrecordfutures` rreportzMessageStatusPublisher.report~s /**  F2&&ww,--  F ~%%u%55 9:: ''''MMQ&MM''''''''''''''' NNE    F'?"."4"4"6"6#&77+@"#E#E*-'',a++#    Z&&t}feVLLFF    N " " $ $ $ FF    !C!C!C!CDDDDDs$6BBB"D::#E! E!rkrYc,||jsdS|j|d< tj|}ddi}t r t |d<t jt||d}t j |t5}| ddddS#1swxYwYdS#t$r(}td|||Yd}~dSd}~wwxYw) Niaidz Content-Typezapplication/jsonz X-API-KeyPOST)dataheadersrY)timeoutz.msg-status: POST failed stage=%s method=%s: %r)rTr;jsondumpsencode_PROXY_API_KEYurllibrequestRequest_PUBLISH_ENDPOINTurlopen _POST_TIMEOUTr1 ExceptionrRre) rrkrVrYpayloadrrreqrespes rrhzMessageStatusPublisher._do_posts}   """z  Fv j((//11G%'9:G 6'5 $.((! )C '']'CC t                       NN@           s<B C!2C C!CC!CC!! D+DDc<|jddS)NF)wait)rBshutdownrs rrzMessageStatusPublisher.shutdowns! '''''rr&)r'r(r)rr+rJrPrTr,r strrmrhrr-rrr7r7Vs . . . .S 7S7777 %%%% *E$*Ec*E#*E$*E*E*E*EXtC4((((((rr7)%r*atexitconcurrent.futuresr>rtloggingosrrf urllib.errorrxurllib.requestr'defence360agent.internals.feature_flagsrr getLoggerr'rRr0environrc _PROXY_URLrstripr{rwr}rArDr rr5r7 publisherregisterrmessage_id_genr-rrrs(       8 $ $ # Z^^/1I J J %%c**-CC  7<<   99999999.Co(o(o(o(o(o(o(o(d # " $ $  "###rdefence360agent/internals/__pycache__/persistent_message.cpython-311.opt-1.pyc0000644000000000000000000001766600000000000024342 0ustar r_j"rddlZddlmZddlmZmZddlmZddlm Z ee Z GddZ dS)N) getLogger)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)db) MessageToSendceZdZdZddZdefdZddZdefd Z defd Z defd Z d eddfd Z dede ddfdZdefdZdefdZedefdZedefdZdde fdZdeeee fddfdZdS)PersistentMessagesQueuea The queue to store messages sent to the server if it is unavailable. - stores more recent data; if a limit is exceeded, older messages are deleted. - no duplicate messages are sent NOTE: it is worth remembering that when writing a large number of messages, the amount of memory used may increase by the size of the sqlite cache (this may not be immediately obvious). https://www.sqlite.org/pragma.html#pragma_cache_size Nch||_||_g|_|pt|_d|_d|_dSNr) _buffer_limit_storage_limit_bufferr_model dropped_total_evicted)self buffer_limit storage_limitmodels a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/persistent_message.py__init__z PersistentMessagesQueue.__init__s8)+ ,}  returnc$|jdc}|_|S)z>Evictions since the last call, then reset (delta for metrics).r)r)revicteds r pop_evictedz#PersistentMessagesQueue.pop_evicted#s!%rc|jrtj5|j|j|j|jz }|dkru|j|}|xj|z c_ttr|xj |z c_ t d||j|jg|_ddddS#1swxYwYdSdS)NrzcPersistent message queue overflow: dropped %d oldest message(s), storage_limit=%d, dropped_total=%d)rratomicr insert_many storage_sizer delete_oldrrrrloggerwarning)rneed_to_removeremoveds rpush_buffer_to_storagez.PersistentMessagesQueue.push_buffer_to_storage(s; < " " " '' 555!%!2T5H!H!A%%"k44^DDG&&'1&&!"ABB1 0 NNJ+*  " ) " " " " " " " " " " " " " " " " " " " "sB1CC Ccg}tj5|t|j|jj|jjz }|j dddn #1swxYwY||j z }g|_ t|SN) rr listrselect timestampmessagetuplesdeleteexecutersortedritemss rpop_allzPersistentMessagesQueue.pop_all@s Y[[ + + T ""K)4;+>&(( E K   ( ( * * *  + + + + + + + + + + + + + + +  e}}sA=BB#&B#crt|jS)z~Return stored rows as (id, timestamp, message) oldest-first without deleting (buffer is neither flushed nor included).)r+rget_all_orderedr/rs r peek_storedz#PersistentMessagesQueue.peek_storedMs,DK//1188::;;;rc$|jgc}|_|S)z>Return and clear the in-memory buffer as (timestamp, message).)rr3s r drain_bufferz$PersistentMessagesQueue.drain_bufferRs"lBt| ridsc|rHtj5|j|ddddS#1swxYwYdSdSr*)rr r delete_in)rr<s rr0zPersistentMessagesQueue.deleteWs  + + + %%c*** + + + + + + + + + + + + + + + + + + + +s>AA message_idr.ctj5|j||ddddS#1swxYwYdSr*)rr r set_message)rr?r.s rupdate_messagez&PersistentMessagesQueue.update_message\s Y[[ 9 9 K # #J 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9s=AAc2|dkSr )qsizer8s remptyzPersistentMessagesQueue.empty`szz||q  rc:|jt|jzSr*)r"lenrr8s rrDzPersistentMessagesQueue.qsizecs 3t|#4#444rc*t|jSr*)rGrr8s r buffer_sizez#PersistentMessagesQueue.buffer_sizefs4<   rcX|jSr*)rr,countr8s rr"z$PersistentMessagesQueue.storage_sizejs"{!!##))+++rc|tj}|j||f|j|jkr|dSdSr*)timerappendrIrr()rr.r-s rputzPersistentMessagesQueue.putns_   I Y0111  t1 1 1  ' ' ) ) ) ) ) 2 1rmessagesc|j||j|jkr|dSdSr*)rextendrIrr()rrPs rput_manyz PersistentMessagesQueue.put_manyusI H%%%  t1 1 1  ' ' ) ) ) ) ) 2 1r)r r N)rNr*)__name__ __module__ __qualname____doc__rintrr(r+r5r9r;r0bytesrBboolrErDpropertyrIr"rOtuplefloatrSrrr r s  S """"0     rcs .-----@@@@@@ 8  j*j*j*j*j*j*j*j*j*j*rdefence360agent/internals/__pycache__/persistent_message.cpython-311.pyc0000644000000000000000000001766600000000000023403 0ustar r_j"rddlZddlmZddlmZmZddlmZddlm Z ee Z GddZ dS)N) getLogger)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)db) MessageToSendceZdZdZddZdefdZddZdefd Z defd Z defd Z d eddfd Z dede ddfdZdefdZdefdZedefdZedefdZdde fdZdeeee fddfdZdS)PersistentMessagesQueuea The queue to store messages sent to the server if it is unavailable. - stores more recent data; if a limit is exceeded, older messages are deleted. - no duplicate messages are sent NOTE: it is worth remembering that when writing a large number of messages, the amount of memory used may increase by the size of the sqlite cache (this may not be immediately obvious). https://www.sqlite.org/pragma.html#pragma_cache_size Nch||_||_g|_|pt|_d|_d|_dSNr) _buffer_limit_storage_limit_bufferr_model dropped_total_evicted)self buffer_limit storage_limitmodels a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/persistent_message.py__init__z PersistentMessagesQueue.__init__s8)+ ,}  returnc$|jdc}|_|S)z>Evictions since the last call, then reset (delta for metrics).r)r)revicteds r pop_evictedz#PersistentMessagesQueue.pop_evicted#s!%rc|jrtj5|j|j|j|jz }|dkru|j|}|xj|z c_ttr|xj |z c_ t d||j|jg|_ddddS#1swxYwYdSdS)NrzcPersistent message queue overflow: dropped %d oldest message(s), storage_limit=%d, dropped_total=%d)rratomicr insert_many storage_sizer delete_oldrrrrloggerwarning)rneed_to_removeremoveds rpush_buffer_to_storagez.PersistentMessagesQueue.push_buffer_to_storage(s; < " " " '' 555!%!2T5H!H!A%%"k44^DDG&&'1&&!"ABB1 0 NNJ+*  " ) " " " " " " " " " " " " " " " " " " " "sB1CC Ccg}tj5|t|j|jj|jjz }|j dddn #1swxYwY||j z }g|_ t|SN) rr listrselect timestampmessagetuplesdeleteexecutersortedritemss rpop_allzPersistentMessagesQueue.pop_all@s Y[[ + + T ""K)4;+>&(( E K   ( ( * * *  + + + + + + + + + + + + + + +  e}}sA=BB#&B#crt|jS)z~Return stored rows as (id, timestamp, message) oldest-first without deleting (buffer is neither flushed nor included).)r+rget_all_orderedr/rs r peek_storedz#PersistentMessagesQueue.peek_storedMs,DK//1188::;;;rc$|jgc}|_|S)z>Return and clear the in-memory buffer as (timestamp, message).)rr3s r drain_bufferz$PersistentMessagesQueue.drain_bufferRs"lBt| ridsc|rHtj5|j|ddddS#1swxYwYdSdSr*)rr r delete_in)rr<s rr0zPersistentMessagesQueue.deleteWs  + + + %%c*** + + + + + + + + + + + + + + + + + + + +s>AA message_idr.ctj5|j||ddddS#1swxYwYdSr*)rr r set_message)rr?r.s rupdate_messagez&PersistentMessagesQueue.update_message\s Y[[ 9 9 K # #J 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9s=AAc2|dkSr )qsizer8s remptyzPersistentMessagesQueue.empty`szz||q  rc:|jt|jzSr*)r"lenrr8s rrDzPersistentMessagesQueue.qsizecs 3t|#4#444rc*t|jSr*)rGrr8s r buffer_sizez#PersistentMessagesQueue.buffer_sizefs4<   rcX|jSr*)rr,countr8s rr"z$PersistentMessagesQueue.storage_sizejs"{!!##))+++rc|tj}|j||f|j|jkr|dSdSr*)timerappendrIrr()rr.r-s rputzPersistentMessagesQueue.putns_   I Y0111  t1 1 1  ' ' ) ) ) ) ) 2 1rmessagesc|j||j|jkr|dSdSr*)rextendrIrr()rrPs rput_manyz PersistentMessagesQueue.put_manyusI H%%%  t1 1 1  ' ' ) ) ) ) ) 2 1r)r r N)rNr*)__name__ __module__ __qualname____doc__rintrr(r+r5r9r;r0bytesrBboolrErDpropertyrIr"rOtuplefloatrSrrr r s  S """"0     rcs .-----@@@@@@ 8  j*j*j*j*j*j*j*j*j*j*rdefence360agent/internals/__pycache__/the_sink.cpython-311.opt-1.pyc0000644000000000000000000004747700000000000022245 0ustar r_jZ0ddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z ddl m Z ddlmZmZddlmZmZmZddlmZddlmZmZmZdd lmZejeZeZ ej!d d d gZ"Gd de Z#GddeZ$dZ%Gdde&Z'Gdde&Z(Gddej)Z*dS)N) attrgetter)MessageReject)BaseMessageProcessor) is_enabledmqtt_tracked_methods)Genmessage_id_gen publisher)safe_cancel_task)DAY ServiceBase rate_limit)gProcessingMessagemessage start_timec2eZdZdZdZdZdZdZdZdS)TheSinkct|td|_||_t |t |j|_|t_dS)NPROCESSING_ORDER)key) sortedr_sinks_ordered_loop TaskManagerMessageProcessor _task_managerrsink)self sink_listloops W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/the_sink.py__init__zTheSink.__init__#s`$ :&899    ( "4#677  c8|jjd|jjS)N.) __class__ __module____name__r s r#__repr__zTheSink.__repr__-s .333T^5L5LMMr%cfd|jD}t|dks Jdtt|dS)ze introspection: decompose a specific role :return classobj: instance or None c4g|]}t||S) isinstance).0rclassobjs r# z%TheSink.decompose..5s8   JtX4N4N    r%zAmbiguous requestN)rlennextiter)r r2optionss ` r# decomposezTheSink.decompose0sf     !0   7||q   "5   DMM4(((r%c8|jdS)z Make sure to run message processing bus only when every MessageSource (or MessageSource+MessageSink mix) got initialized N)rstartr+s r#r;z TheSink.start;s   """""r%cXKtd|jtd|jdd{Vtd|jd{VdS)Nzshutdown the sink startedzwait for current taskstimeoutzfinish wait task)loggerinfor should_stopwait_current_taskswaitr+s r#shutdownzTheSink.shutdownCs /000 &&((( ,--- 33A3>>>>>>>>> &''' %%'''''''''''r%cJK|j|d{VdSN)rpush_msg)r rs r#process_messagezTheSink.process_messageKs5 ))'22222222222r%N) r*r) __qualname__r$r,r9r;rErIr/r%r#rr"sqNNN ) ) )###(((33333r%rcpeZdZdZdZdZfdZdZedZ d dZ d Z d Z e d ZxZS) rir=c~t|t|j|_|j|_|j|_||_ tj |_ tttj|_|tj|_dS)N)maxsize)periodon_drop)superr$ MessageQueueMAXSIZE_queue CONCURRENCY _concurrencyTIMEOUT_process_message_timeout_msg_processorweakrefWeakSettasksrr r@warning_throttled_loggererrorthrottled_log_error)r r" msg_processorr(s r#r$zTaskManager.__init__Ws "4<888  ,(, %+_&& !+3!O!O!O#'#9#9&,#G#G   r%c,K|js/|jt|d{VdS|jjrd|j|j|f}n"d|j|j|f}|j|dS)z&Push message unless the queue is full.NzNMessage queue is full %s. Current processing messages: %s. Message ignored: %szZMessage queue is full. Queue size: %s Current processing messages: %s. Message ignored: %s) rTfullputMessageComparabler^should_be_calledcurrent_processing_messagesqsizer`)r msgargss r#rHzTaskManager.push_msgas{!! ,+//"3C"8"899 9 9 9 9 9 9 9 9 9%6 OK4OK%%''4 %D $d + + + +r%c>td|jDS)Nc3K|]R}||jjtt j|jjz dfVSdS)N)doneprocessing_msgrroundtime monotonicr)r1tasks r# z:TaskManager.current_processing_messages..sq  99;;  #+dn&&)<)GGKK       r%)tupler\r+s r#rgz'TaskManager.current_processing_messages|s6         r%NcK|jrOd|jD}td|t j|j|d{VdSdS)Ncjg|]0\}}|d|d|f1S)method message_id)get)r1mlastings r#r3z2TaskManager.wait_current_tasks..sIAwx!%% "5"5w?r%z#Waiting for %r processing to finishr>)r\rgr@rAasynciorD)r r?msg_to_processs r#rCzTaskManager.wait_current_taskss : <"&"BN KK5   ,tz7;;; ; ; ; ; ; ; ; ; ; < z"TaskManager._run..si.?.?.A.Ar%z3There is still %s unprocessed messages in the queue Error during message processing:)r}BoundedSemaphorerV _should_stopr@debugrTrh_TaskManager__limit_concurrencyrzCancelledErrorr create_taskrYrirrqrrroadd_done_callback_on_msg_processedr\addr] exception)r msg_comparablet unprocessedrs @r#_runzTaskManager._runs,T->??  A' " 5t{7H7H7J7JKKK229=========+/;??+<+<%<%<%<%<%<%Nz+Message hasn't been processed in %s seconds)r}wait_foracquirerX TimeoutErrorr`)r rs r#__limit_concurrencyzTaskManager.__limit_concurrencys  $-%%'' 9'   ((A1  s28*A%$A%cn|}|rtd|dSdS)Nr)exc_info)rr@)futurees r#rzTaskManager._on_msg_processedsH       M   ?!  L L L L L M Mr%rG)r*r)rJrSrUrWr$rHpropertyrgrCrr staticmethodr __classcell__r(s@r#rrOsGKGHHHHH,,,6   X   < < < <AAA8   MM\MMMMMr%rc`K|st|d{VdSdSrG)rnr )rss r# cancel_taskrsF 99;;%t$$$$$$$$$$$%%r%c$eZdZdZdZdZdZdS)rrLc||_tj|_t dt j|_dS)NrL)rO)sinksrZWeakValueDictionarylocksrr@r_r`)r rs r#r$zMessageProcessor.__init__sC 022 #=:W#=#=#= L$ $    r%cZK|d}|rv|j|tj}|4d{V||d{Vdddd{VdS#1d{VswxYwYdS||d{VdS)N attackers_ip)rzr setdefaultr}Lock_call_unlocked)r riiplocks r#__call__zMessageProcessor.__call__sT WW^ $ $  +:((W\^^<zRejected: %s -> %r)filerzCMessage %r was not processed in the %r plugin in %ss; Traceback: %szError processing %r in %rz%s processed in %.4f secondszE%s message took longer to process than expected (%.4f sec > %.4f sec))$rrzrr enrichr report_reporter_gen_sinkrqrrrr}rrIrshieldTIMEOUT_TO_SINK_PROCESSr0rrrrr@rAstrrioStringIO print_stackseekr_read ExceptionrPROCESSING_TIME_THRESHOLDr`) r rir;rprocess_message_task processedrstackprocessing_times r#rzMessageProcessor._call_unlockeds  ' ' '!!%9%;%;;;%S00  !# & & &08MNNNN  J* 8* 8D) 8'.':((--(($#*"2 N#788 8 ### Di11$#C7)   8""6777777777777    0#a&&#>>>2""67777777777771'     $00e0<<< 1  $0JJLL ""6777777777777     !N)r*r)rJrr$rrr/r%r#rrsL"   +++EEEEEr%rcBeZdZdZdZefdZdZdZxZ S)rez#Wrapper to make message comparable.c|xjdz c_t|}|j|jf|_||_|SNr4)indexrQ__new__PRIORITYpriorityri)clsrirvr(s r#rzMessageComparable.__new__/sC Q WW__S ! !lCI-  r%c@|j|jSrG)r__lt__)r others r#rzMessageComparable.__lt__7s}##EN333r%cZd|jj|j|jS)Nz'<{klass}({msg!r}), priority={priority}>)klassrir)formatr(r*rirr+s r#r,zMessageComparable.__repr__:s28??.)]@   r%) r*r)rJ__doc__rrrrr,rrs@r#rere)sm-- E\444       r%rec4eZdZfdZdeffd ZdZxZS)rRctj|i|tj|_d|j_d|j_dS)N2i)rQr$reprlibRepr_repr maxstringmaxtuple)r rjkwargsr(s r#r$zMessageQueue.__init__CsF$)&)))\^^ ! " r%itemcVKt|d{VSrG)rQrd)r rr(s r#rdzMessageQueue.putIs/WW[[&&&&&&&&&r%cttjd|jDdd}d|jd|d|j|dS) Nc0g|]}|jjjSr/)rir(rJ)r1rs r#r3z(MessageQueue.__str__..Ps IIIT#0IIIr%c|dSrr/)rs r#rz&MessageQueue.__str__..Rs T!Wr%T)rreversez) r collectionsCounterrTitemsrNrhrrepr)r msg_countss r#__str__zMessageQueue.__str__Ls  IIT[III  egg$$     rsh  >>>>>>>>BBBBBB 322222EEEEEEEEEE444444  8 $ $SUU*K*)\2 *3*3*3*3*3"*3*3*3ZwMwMwMwMwM+wMwMwMt%%% XXXXXvXXXv        2     7(     r%defence360agent/internals/__pycache__/the_sink.cpython-311.pyc0000644000000000000000000004747700000000000021306 0ustar r_jZ0ddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z ddl m Z ddlmZmZddlmZmZmZddlmZddlmZmZmZdd lmZejeZeZ ej!d d d gZ"Gd de Z#GddeZ$dZ%Gdde&Z'Gdde&Z(Gddej)Z*dS)N) attrgetter)MessageReject)BaseMessageProcessor) is_enabledmqtt_tracked_methods)Genmessage_id_gen publisher)safe_cancel_task)DAY ServiceBase rate_limit)gProcessingMessagemessage start_timec2eZdZdZdZdZdZdZdZdS)TheSinkct|td|_||_t |t |j|_|t_dS)NPROCESSING_ORDER)key) sortedr_sinks_ordered_loop TaskManagerMessageProcessor _task_managerrsink)self sink_listloops W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/internals/the_sink.py__init__zTheSink.__init__#s`$ :&899    ( "4#677  c8|jjd|jjS)N.) __class__ __module____name__r s r#__repr__zTheSink.__repr__-s .333T^5L5LMMr%cfd|jD}t|dks Jdtt|dS)ze introspection: decompose a specific role :return classobj: instance or None c4g|]}t||S) isinstance).0rclassobjs r# z%TheSink.decompose..5s8   JtX4N4N    r%zAmbiguous requestN)rlennextiter)r r2optionss ` r# decomposezTheSink.decompose0sf     !0   7||q   "5   DMM4(((r%c8|jdS)z Make sure to run message processing bus only when every MessageSource (or MessageSource+MessageSink mix) got initialized N)rstartr+s r#r;z TheSink.start;s   """""r%cXKtd|jtd|jdd{Vtd|jd{VdS)Nzshutdown the sink startedzwait for current taskstimeoutzfinish wait task)loggerinfor should_stopwait_current_taskswaitr+s r#shutdownzTheSink.shutdownCs /000 &&((( ,--- 33A3>>>>>>>>> &''' %%'''''''''''r%cJK|j|d{VdSN)rpush_msg)r rs r#process_messagezTheSink.process_messageKs5 ))'22222222222r%N) r*r) __qualname__r$r,r9r;rErIr/r%r#rr"sqNNN ) ) )###(((33333r%rcpeZdZdZdZdZfdZdZedZ d dZ d Z d Z e d ZxZS) rir=c~t|t|j|_|j|_|j|_||_ tj |_ tttj|_|tj|_dS)N)maxsize)periodon_drop)superr$ MessageQueueMAXSIZE_queue CONCURRENCY _concurrencyTIMEOUT_process_message_timeout_msg_processorweakrefWeakSettasksrr r@warning_throttled_loggererrorthrottled_log_error)r r" msg_processorr(s r#r$zTaskManager.__init__Ws "4<888  ,(, %+_&& !+3!O!O!O#'#9#9&,#G#G   r%c,K|js/|jt|d{VdS|jjrd|j|j|f}n"d|j|j|f}|j|dS)z&Push message unless the queue is full.NzNMessage queue is full %s. Current processing messages: %s. Message ignored: %szZMessage queue is full. Queue size: %s Current processing messages: %s. Message ignored: %s) rTfullputMessageComparabler^should_be_calledcurrent_processing_messagesqsizer`)r msgargss r#rHzTaskManager.push_msgas{!! ,+//"3C"8"899 9 9 9 9 9 9 9 9 9%6 OK4OK%%''4 %D $d + + + +r%c>td|jDS)Nc3K|]R}||jjtt j|jjz dfVSdS)N)doneprocessing_msgrroundtime monotonicr)r1tasks r# z:TaskManager.current_processing_messages..sq  99;;  #+dn&&)<)GGKK       r%)tupler\r+s r#rgz'TaskManager.current_processing_messages|s6         r%NcK|jrOd|jD}td|t j|j|d{VdSdS)Ncjg|]0\}}|d|d|f1S)method message_id)get)r1mlastings r#r3z2TaskManager.wait_current_tasks..sIAwx!%% "5"5w?r%z#Waiting for %r processing to finishr>)r\rgr@rAasynciorD)r r?msg_to_processs r#rCzTaskManager.wait_current_taskss : <"&"BN KK5   ,tz7;;; ; ; ; ; ; ; ; ; ; < z"TaskManager._run..si.?.?.A.Ar%z3There is still %s unprocessed messages in the queue Error during message processing:)r}BoundedSemaphorerV _should_stopr@debugrTrh_TaskManager__limit_concurrencyrzCancelledErrorr create_taskrYrirrqrrroadd_done_callback_on_msg_processedr\addr] exception)r msg_comparablet unprocessedrs @r#_runzTaskManager._runs,T->??  A' " 5t{7H7H7J7JKKK229=========+/;??+<+<%<%<%<%<%<%Nz+Message hasn't been processed in %s seconds)r}wait_foracquirerX TimeoutErrorr`)r rs r#__limit_concurrencyzTaskManager.__limit_concurrencys  $-%%'' 9'   ((A1  s28*A%$A%cn|}|rtd|dSdS)Nr)exc_info)rr@)futurees r#rzTaskManager._on_msg_processedsH       M   ?!  L L L L L M Mr%rG)r*r)rJrSrUrWr$rHpropertyrgrCrr staticmethodr __classcell__r(s@r#rrOsGKGHHHHH,,,6   X   < < < <AAA8   MM\MMMMMr%rc`K|st|d{VdSdSrG)rnr )rss r# cancel_taskrsF 99;;%t$$$$$$$$$$$%%r%c$eZdZdZdZdZdZdS)rrLc||_tj|_t dt j|_dS)NrL)rO)sinksrZWeakValueDictionarylocksrr@r_r`)r rs r#r$zMessageProcessor.__init__sC 022 #=:W#=#=#= L$ $    r%cZK|d}|rv|j|tj}|4d{V||d{Vdddd{VdS#1d{VswxYwYdS||d{VdS)N attackers_ip)rzr setdefaultr}Lock_call_unlocked)r riiplocks r#__call__zMessageProcessor.__call__sT WW^ $ $  +:((W\^^<zRejected: %s -> %r)filerzCMessage %r was not processed in the %r plugin in %ss; Traceback: %szError processing %r in %rz%s processed in %.4f secondszE%s message took longer to process than expected (%.4f sec > %.4f sec))$rrzrr enrichr report_reporter_gen_sinkrqrrrr}rrIrshieldTIMEOUT_TO_SINK_PROCESSr0rrrrr@rAstrrioStringIO print_stackseekr_read ExceptionrPROCESSING_TIME_THRESHOLDr`) r rir;rprocess_message_task processedrstackprocessing_times r#rzMessageProcessor._call_unlockeds  ' ' '!!%9%;%;;;%S00  !# & & &08MNNNN  J* 8* 8D) 8'.':((--(($#*"2 N#788 8 ### Di11$#C7)   8""6777777777777    0#a&&#>>>2""67777777777771'     $00e0<<< 1  $0JJLL ""6777777777777     !N)r*r)rJrr$rrr/r%r#rrsL"   +++EEEEEr%rcBeZdZdZdZefdZdZdZxZ S)rez#Wrapper to make message comparable.c|xjdz c_t|}|j|jf|_||_|SNr4)indexrQ__new__PRIORITYpriorityri)clsrirvr(s r#rzMessageComparable.__new__/sC Q WW__S ! !lCI-  r%c@|j|jSrG)r__lt__)r others r#rzMessageComparable.__lt__7s}##EN333r%cZd|jj|j|jS)Nz'<{klass}({msg!r}), priority={priority}>)klassrir)formatr(r*rirr+s r#r,zMessageComparable.__repr__:s28??.)]@   r%) r*r)rJ__doc__rrrrr,rrs@r#rere)sm-- E\444       r%rec4eZdZfdZdeffd ZdZxZS)rRctj|i|tj|_d|j_d|j_dS)N2i)rQr$reprlibRepr_repr maxstringmaxtuple)r rjkwargsr(s r#r$zMessageQueue.__init__CsF$)&)))\^^ ! " r%itemcVKt|d{VSrG)rQrd)r rr(s r#rdzMessageQueue.putIs/WW[[&&&&&&&&&r%cttjd|jDdd}d|jd|d|j|dS) Nc0g|]}|jjjSr/)rir(rJ)r1rs r#r3z(MessageQueue.__str__..Ps IIIT#0IIIr%c|dSrr/)rs r#rz&MessageQueue.__str__..Rs T!Wr%T)rreversez) r collectionsCounterrTitemsrNrhrrepr)r msg_countss r#__str__zMessageQueue.__str__Ls  IIT[III  egg$$     rsh  >>>>>>>>BBBBBB 322222EEEEEEEEEE444444  8 $ $SUU*K*)\2 *3*3*3*3*3"*3*3*3ZwMwMwMwMwM+wMwMwMt%%% XXXXXvXXXv        2     7(     r%defence360agent/internals/auth_protocol.py0000644000000000000000000000226600000000000016007 0ustar import asyncio import socket import logging import struct logger = logging.getLogger(__name__) class UnixSocketAuthProtocol(asyncio.Protocol): """ This protocol uses SO_PEERCRED attribute of unix socket to get authentication data (pid, uid, gid) After connect, this values are stored in object's _pid, _uid, _gid attributes """ # ucred struct format (3 integers) # struct ucred # { # pid_t pid; /* PID of sending process. */ # uid_t uid; /* UID of sending process. */ # gid_t gid; /* GID of sending process. */ # }; # STRUCT_FORMAT = "3i" def connection_made(self, transport): self._transport = transport conn = self._transport.get_extra_info("socket") creds = conn.getsockopt( socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize(self.STRUCT_FORMAT), ) self._pid, self._uid, self._gid = struct.unpack( self.STRUCT_FORMAT, creds ) logger.debug( "New socket connection from pid=%s, uid=%s, gid=%s", self._pid, self._uid, self._gid, ) defence360agent/internals/cln.py0000644000000000000000000003303100000000000013673 0ustar import asyncio import json import logging import os import socket import urllib.error import urllib.parse import urllib.request from collections import defaultdict from pathlib import Path from urllib.parse import parse_qsl, urlencode, urljoin, urlparse, urlunparse import psutil from defence360agent.contracts.config import ANTIVIRUS_MODE from defence360agent.contracts.license import LicenseCLN from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.utils import CheckRunError, async_lru_cache, check_run from defence360agent.utils.common import get_hostname _TIMEOUT = 300 # timeout for network operations _IMUNIFY_EMAIL_CONFIG_EXECUTABLE = Path("/usr/sbin/ie-config") logger = logging.getLogger(__name__) IE_SUPPORTED_CMD = ( "wget -qq -O -" " https://repo.imunify360.cloudlinux.com/defence360/imunifyemail-deploy.sh" " | bash -s 'is-supported'" ) @async_lru_cache(maxsize=1) async def is_imunify_email_supported() -> bool: try: await check_run(IE_SUPPORTED_CMD, shell=True) except CheckRunError as e: if e.returncode != 100: logger.error(f"imunify-email check failed {str(e)}") return False return True async def get_imunify_email_status(): """Try to get imunify-email status""" if ANTIVIRUS_MODE: return False if not _IMUNIFY_EMAIL_CONFIG_EXECUTABLE.exists(): return False try: output = await check_run( [str(_IMUNIFY_EMAIL_CONFIG_EXECUTABLE), "status"] ) except CheckRunError: return False return "spamfilter exim configuration: enabled" in output.decode() class CLNError(Exception): def __init__(self, status=None, message=None): self.message = message self.status = status def __str__(self): if self.message: return self.message return "Unexpected status code from CLN: {}".format(self.status) class InvalidLicenseError(Exception): pass class BackupNotFound(CLNError): GB = 1024 * 1024 * 1024 def __init__(self, url): self.url = url def __str__(self): return "Backup not found in CLN" def add_used_space(self): if self.url is None: return pu = urlparse(self.url) query = dict(parse_qsl(pu.query)) query["used_space"] = self._disk_usage() return urlunparse( ( pu.scheme, pu.netloc, pu.path, pu.params, urlencode(query), pu.fragment, ) ) def _disk_usage(self): total_used = 0 partitions = psutil.disk_partitions() processed = set() for p in partitions: if ( (p.device not in processed) and ("noauto" not in p.opts) and (not p.device.startswith("/dev/loop")) ): total_used += psutil.disk_usage(p.mountpoint).used processed.add(p.device) return round(total_used / self.GB) def _post_request(url, data=None, headers=None, timeout=None): """To be used by RestCLN._request().""" kwargs = {} if headers is not None: kwargs["headers"] = headers if data is not None: if isinstance(data, bytes): kwargs.setdefault( "headers", {"Content-type": "application/octet-stream"} ) elif isinstance(data, str): data = data.encode("utf-8") kwargs.setdefault( "headers", {"Content-type": "text/plain; charset=utf-8"} ) else: # dict data = urllib.parse.urlencode(data).encode("ascii") kwargs.setdefault( "headers", {"Content-type": "application/x-www-form-urlencoded"}, ) kwargs["data"] = data try: resp = urllib.request.urlopen( urllib.request.Request(url, **kwargs), timeout=timeout ) except urllib.error.HTTPError as e: # Handle HTTP errors (400, 500, etc.) if e.code < 400: raise CLNError(e.code) from e # e.code >= 400 message = None if e.fp is not None: logger.warning( "CLN.post(url=%r, data=%r, headers=%r): %d %s", url, data, headers, e.code, e.reason, ) try: resp_data = e.read() except socket.timeout: raise TimeoutError("Timed out reading error message") # the response may be non-json message = resp_data.decode(errors="replace") raise CLNError(message=message, status=e.code) from e except urllib.error.URLError as e: # consider this as a network error (DNS resolution failed) raise CLNError(message=str(e)) from e except socket.timeout: raise TimeoutError("Timed out receiving response") except OSError as e: logger.warning( "CLN.post(url=%r, data=%r, headers=%r, timeout=%r): %s", url, data, headers, timeout, e, ) raise else: with resp: if resp.code == 204: return resp.code, None elif resp.code in (200, 244): # 244 - /im/ab/check returns link for backup buy page try: content = resp.read() except socket.timeout: raise TimeoutError("Timed out reading response") else: try: return resp.code, json.loads(content.decode()) except json.JSONDecodeError as e: raise CLNError( message=( f"Non-json data from CLN: {content} for" f" code={resp.code}" ), status=resp.code, ) from e else: raise CLNError(resp.code) class RestCLN: _URL_PATH_TEMPLATE = "https://{domain}/api/im/" _BASE_DOMAIN_NAME = "cln.cloudlinux.com" _IPV6_DOMAIN_NAME = os.environ.get( "IM360_CLN_API_BASE_URL", "ipv6.cln.cloudlinux.com" ) _IPV4_DOMAIN_NAME = os.environ.get( "IM360_CLN_API_BASE_URL", "ipv4.cln.cloudlinux.com" ) _BASE_URL = _URL_PATH_TEMPLATE.format( domain=os.environ.get("IM360_CLN_API_BASE_URL", _BASE_DOMAIN_NAME) ) _REGISTER_URL = urljoin(_BASE_URL, "register") _UNREGISTER_URL = urljoin(_BASE_URL, "unregister") _CHECKIN_URL = urljoin(_BASE_URL, "checkin") _ACRONIS_CREDENTIALS_URL = urljoin(_BASE_URL, "ab/credentials") _ACRONIS_REMOVE_URL = urljoin(_BASE_URL, "ab/remove") _ACRONIS_CHECK_URL = urljoin(_BASE_URL, "ab/check") STATUS_OK_PAID_LICENSE = "ok" STATUS_OK_TRIAL_LICENSE = "ok-trial" @classmethod async def _request(cls, url, *, data=None, headers=None, timeout=_TIMEOUT): return await asyncio.get_event_loop().run_in_executor( None, _post_request, url, data, headers, timeout ) @classmethod async def process_ipl_licence(cls): v4_license_url = urljoin( cls._URL_PATH_TEMPLATE.format(domain=cls._IPV4_DOMAIN_NAME), "register", ) data = {"key": "IPL", "hostname": get_hostname()} try: _, token = await cls._request(v4_license_url, data=data) except CLNError as cln_error: if cln_error.status == 404: v6_license_url = urljoin( cls._URL_PATH_TEMPLATE.format( domain=cls._IPV6_DOMAIN_NAME ), "register", ) _, token = await cls._request(v6_license_url, data=data) else: raise cln_error return token @classmethod async def register(cls, key: str) -> dict: """ Register server with key :param key: registration key :return: license token in case of success """ if key == "IPL": return await cls.process_ipl_licence() _, token = await cls._request( cls._REGISTER_URL, data={"key": key, "hostname": get_hostname()}, ) return token @classmethod async def checkin( cls, server_id: str, users_count: int, hostname: str = None, ): """ Update license token :param str server_id: server id :param int users_count: users count :param str hostname: current server hostname :return: dict new license token """ hostname = hostname or get_hostname() imunify_email_status = await get_imunify_email_status() panel = HostingPanel() try: panel_name = await panel.name() except Exception as e: logger.error( "Failed to get panel version: %s", str(e), exc_info=True ) panel_name = panel.NAME req = { "id": server_id, "hostname": hostname, "im": { "users": users_count, "panel": panel_name, "imunifyEmail": imunify_email_status, "supported_features": { "IM_EMAIL": await is_imunify_email_supported(), }, }, } data = json.dumps(req) logger.info("CLN checkin: %s", data) _, token = await cls._request( cls._CHECKIN_URL, data=data, headers={"Content-type": "application/json"}, ) return token @classmethod async def acronis_credentials(cls, server_id: str) -> dict: """ Creates Acronis Backup account and get user & password :param server_id: server id """ _, creds = await cls._request( cls._ACRONIS_CREDENTIALS_URL, data={"id": server_id} ) return creds @classmethod async def acronis_remove(cls, server_id: str): """ Removes Acronis Backup account :param server_id: server id """ await cls._request(cls._ACRONIS_REMOVE_URL, data={"id": server_id}) @classmethod async def acronis_check(cls, server_id: str) -> dict: """ If Acronis account exists return backup size in GB or if backups not exists URL for backups :param server_id: server id """ status, response = await cls._request( cls._ACRONIS_CHECK_URL, data={"id": server_id} ) if status == 244: # Backup not found raise BackupNotFound(url=None) # Prohibit purchasing a new backup return response @classmethod async def unregister(cls, server_id=None): """ Unregister server id :return: None """ server_id = server_id or LicenseCLN.get_server_id() await cls._request(cls._UNREGISTER_URL, data={"id": server_id}) class CLN: _CALLBACKS = defaultdict(set) @classmethod def add_callback_for(cls, method_name, coro_callback): cls._CALLBACKS[method_name].add(coro_callback) @classmethod async def run_callbacks_for(cls, method_name): for callback in cls._CALLBACKS[method_name]: try: await callback() except asyncio.CancelledError: raise except Exception as e: logger.exception( "Error '{!r}' happened when run callback {} for" "CLN {} method".format(e, callback, method_name) ) @classmethod def is_avp_key(cls, key): return key.startswith("IMAVP") @classmethod async def register(cls, key): if cls.is_avp_key(key) and not ANTIVIRUS_MODE: raise InvalidLicenseError( "Imunify360 can not be registered with ImunifyAV+ key" ) license = await RestCLN.register(key) # in case of IP license, we have to register to know if license is # valid for server (i.e. Imunify360 license is used for Imunify360) if not LicenseCLN.is_valid(license): # release registered server id await RestCLN.unregister(license["id"]) raise InvalidLicenseError("License is invalid for this server") LicenseCLN.update(license) await cls.run_callbacks_for("register") @classmethod async def unregister(cls): await RestCLN.unregister() LicenseCLN.delete() await cls.run_callbacks_for("unregister") @classmethod async def refresh_token(cls, token): """Refreshes token and returns new one on success, None otherwise""" if LicenseCLN.is_free(): # noop: free license can not be refreshed return LicenseCLN.get_token() if LicenseCLN.get_token().get("is_alternative"): # self-signed licenses are refreshed by customer return LicenseCLN.get_token() new_token = await RestCLN.checkin(token["id"], LicenseCLN.users_count) logger.info("Got new token from CLN: %s", new_token) if new_token is None: await CLN.unregister() else: LicenseCLN.update(new_token) await cls.run_callbacks_for("refresh_token") return LicenseCLN.get_token() def subscribe_to_license_changes(coro): for method_name in ["register", "unregister", "refresh_token"]: CLN.add_callback_for(method_name, coro_callback=coro) defence360agent/internals/deadlock_detecting_lock.py0000644000000000000000000000136300000000000017726 0ustar import asyncio class DeadlockError(Exception): """Error raised if DeadlockDetectingLock detects deadlock""" class DeadlockDetectingLock: """ Lock that detects deadlock when it is about to be acquired by the same task that already holds it. """ def __init__(self): self._lock = asyncio.Lock() self._owner = None def locked(self): return self._lock.locked() async def __aenter__(self): curr_task = asyncio.current_task() if self._owner == curr_task: raise DeadlockError() await self._lock.acquire() self._owner = curr_task return self async def __aexit__(self, exc_type, exc, tb): self._owner = None self._lock.release() defence360agent/internals/delivery_ack.py0000644000000000000000000000325500000000000015565 0ustar """In-memory delivery acknowledgements for Reportable messages. The send-to-server plugins queue messages rather than deliver them, so a producer that keeps its own copy of the payload cannot tell a delivered message from one a lost send round dropped. Every send path reports the ids the transport accepted here, and producers register the ids they care about. Acknowledgements are deliberately not persisted: one that never arrives leaves the message unconfirmed and makes the producer send it again. That costs a duplicate the server may well store twice, whereas a silently dropped payload cannot be recovered at all. """ import logging from typing import Callable, Optional logger = logging.getLogger(__name__) class DeliveryAckRegistry: def __init__(self) -> None: self._callbacks: dict[str, Callable[[], None]] = {} def watch(self, message_id: str, on_delivered: Callable[[], None]) -> None: if not message_id: return self._callbacks[message_id] = on_delivered def unwatch(self, message_id: str) -> None: self._callbacks.pop(message_id, None) def confirm(self, message_id: Optional[str]) -> None: on_delivered = self._callbacks.pop(message_id, None) if on_delivered is None: return try: on_delivered() except Exception: # a producer's bookkeeping must never break a send round, but it # failing means the producer will re-send forever: log the # traceback, this is the only place that sees it logger.exception( "Delivery acknowledgement for %s failed", message_id ) registry = DeliveryAckRegistry() defence360agent/internals/feature_flags.py0000644000000000000000000002326700000000000015740 0ustar """ Shared reader for the local feature flags file. The file is written by: - Go resident-agent FeatureFlags plugin (IM360 mode) - Python FeatureFlagsSync plugin (AV mode) Other subsystems (e.g. message_status_publisher) use this module to check individual flag values at runtime. Supported JSON shapes on disk (readers / ``is_enabled`` / ``get_params``): - New shape ``{"flags": ["mqtt_tracking"], "params": {"flag": ["A", "B"]}}`` (mirrors the sync API response; carries per-flag string-list params). - Legacy object ``{"mqtt_tracking": true, ...}`` (still accepted). - JSON array of enabled names ``["mqtt_tracking"]`` (still accepted). - Legacy wrapper ``{"flags": ["mqtt_tracking", ...]}`` (still accepted). The sync API checksum collapses to the legacy sorted-names array when no params are present, so this agent and older agents agree on the bool-only case. With params, the canonical form expands to ``{"flags": [...], "params": {...}}`` with all keys and list members sorted. The sync plugin also writes ``FLAGS_PLAIN_PATH`` (``/var/imunify360/feature_flags``): plain text, one enabled flag name per line (sorted), for scripts. """ from __future__ import annotations import hashlib import json import os from typing import Any FLAGS_PATH = "/var/imunify360/feature_flags.json" # Plain list of enabled flag names (one per line), same order as sorted JSON array. FLAGS_PLAIN_PATH = "/var/imunify360/feature_flags" # Flag name whose params list drives MQTT message-status enrichment. MQTT_TRACKED_METHODS_FLAG = "mqtt_tracked_methods" # Gates message-loss observability (drop counters, eviction warnings, loss # metric emission). Same name in the Go resident-agent and the proxy. MESSAGE_LOSS_OBSERVABILITY_FLAG = "message_loss_observability" _cached_flags: dict[str, Any] = {} _cached_params: dict[str, list[str]] = {} # Pre-built frozenset for the MQTT tracked-methods allow-list. Cached # alongside the raw params dict so the hot path (every Reportable message # in the_sink._call_unlocked) avoids re-allocating a fresh frozenset and # the list copy that get_params() would do. Invalidated by the same # file-mtime trigger that invalidates _cached_params. _cached_mqtt_methods: frozenset[str] = frozenset() _cached_mtime: float = 0.0 def _normalize_flags_from_file(raw: Any) -> dict[str, Any]: """Map file JSON to a flat name->value dict for :func:`is_enabled`.""" if raw is None: return {} if isinstance(raw, list): out: dict[str, Any] = {} for item in raw: if isinstance(item, str): out[item] = True return out if isinstance(raw, dict): inner = raw.get("flags") if isinstance(inner, list): return _normalize_flags_from_file(inner) return raw return {} def _params_from_file(raw: Any) -> dict[str, list[str]]: """Extract ``params`` mapping from new-shape file content. Only the new ``{"flags": [...], "params": {name: [...]}}`` shape carries params; every other (legacy) shape returns an empty mapping. """ if not isinstance(raw, dict): return {} raw_params = raw.get("params") if not isinstance(raw_params, dict): return {} out: dict[str, list[str]] = {} for name, values in raw_params.items(): if not isinstance(name, str) or not isinstance(values, list): continue cleaned = [v for v in values if isinstance(v, str)] if cleaned: out[name] = cleaned return out def _read_state() -> tuple[dict[str, Any], dict[str, list[str]]]: global _cached_flags, _cached_params, _cached_mqtt_methods, _cached_mtime try: mtime = os.path.getmtime(FLAGS_PATH) except OSError: _cached_flags = {} _cached_params = {} _cached_mqtt_methods = frozenset() _cached_mtime = 0.0 return _cached_flags, _cached_params if mtime == _cached_mtime: return _cached_flags, _cached_params try: with open(FLAGS_PATH) as f: raw = json.load(f) _cached_flags = _normalize_flags_from_file(raw) _cached_params = _params_from_file(raw) except (OSError, json.JSONDecodeError): _cached_flags = {} _cached_params = {} _cached_mqtt_methods = frozenset( _cached_params.get(MQTT_TRACKED_METHODS_FLAG, ()) ) _cached_mtime = mtime return _cached_flags, _cached_params def _read_flags() -> dict[str, Any]: flags, _ = _read_state() return flags def _read_params() -> dict[str, list[str]]: _, params = _read_state() return params def enabled_flag_names_sorted(flags: Any) -> list[str]: """Return sorted enabled flag names for JSON and plain-text sidecar. Accepts the same shapes as :func:`_normalize_flags_from_file` (array, flat map, ``{"flags": [...]}``) so checksums and sidecars match Go ``enabledNamesSortedForChecksum`` / :func:`is_enabled`. """ if not isinstance(flags, (list, dict)): raise TypeError( f"flags must be list or dict, not {type(flags).__name__}" ) normalized = _normalize_flags_from_file(flags) return sorted(k for k, v in normalized.items() if v) def canonical_sync_flag_list_bytes(names: list[str]) -> bytes: """JSON array bytes used for sync MD5 when no params are present (matches correlation_api ``checksum_for_sync_flag_list``).""" ordered = sorted(names) return json.dumps(ordered, sort_keys=True, indent=2).encode() def canonical_sync_response_bytes( names: list[str], params: dict[str, list[str]] ) -> bytes: """JSON bytes for the sync MD5 over the full response shape. Mirrors correlation_api ``checksum_for_sync_response``: collapses to the legacy sorted-names array when ``params`` is empty so old agents keep matching, otherwise expands to the deterministic ``{"flags": [...], "params": {...}}`` form with all keys and list members sorted. """ if not params: return canonical_sync_flag_list_bytes(names) canonical = { "flags": sorted(names), "params": {k: sorted(v) for k, v in sorted(params.items())}, } return json.dumps(canonical, sort_keys=True, indent=2).encode() def sync_checksum_hex_from_flags_file(path: str) -> str: """MD5 hex of the canonical sync-response form for ``path``. Returns "" if the file is missing or invalid. Computes the same MD5 the server returned, so a matching checksum lets the agent skip the response payload on the next sync. """ try: with open(path, encoding="utf-8") as f: raw = json.load(f) except (OSError, UnicodeDecodeError, json.JSONDecodeError): return "" names = enabled_flag_names_sorted(raw) params = _params_from_file(raw) payload = canonical_sync_response_bytes(names, params) return hashlib.md5(payload, usedforsecurity=False).hexdigest() def legacy_feature_flags_map_bytes(names: list[str]) -> bytes: """On-disk legacy JSON: ``{flag: true, ...}`` with sorted keys.""" d = {n: True for n in sorted({x for x in names if isinstance(x, str)})} return json.dumps(d, sort_keys=True, indent=2).encode() def sync_response_file_bytes( names: list[str], params: dict[str, list[str]] ) -> bytes: """Persisted form for ``FLAGS_PATH`` carrying both flags and params. Same canonical shape as ``canonical_sync_response_bytes`` so the file is self-describing and round-trips through ``sync_checksum_hex_from_flags_file``. """ canonical = { "flags": sorted(names), "params": {k: sorted(v) for k, v in sorted(params.items())}, } return json.dumps(canonical, sort_keys=True, indent=2).encode() def plain_text_payload_for_enabled_flags(flags: Any) -> bytes: """Body for ``FLAGS_PLAIN_PATH``: one name per line, trailing newline if non-empty.""" names = enabled_flag_names_sorted(flags) if not names: return b"" return ("\n".join(names) + "\n").encode() def serialize_feature_flags_file_payload(flags: Any) -> bytes: """Serialize dict flags for writing ``FLAGS_PATH`` (legacy map only).""" if isinstance(flags, dict): return json.dumps(flags, sort_keys=True, indent=2).encode() raise TypeError(f"flags must be dict, not {type(flags).__name__}") def is_enabled(flag_name: str, default: bool = False) -> bool: """Return whether *flag_name* is enabled. If the file is missing, unreadable, or the flag is absent, *default* is returned. Defaults to False so unknown flags are treated as disabled unless the caller explicitly opts in. """ flags = _read_flags() value = flags.get(flag_name) if value is None: return default return bool(value) def get_params(flag_name: str) -> list[str]: """Return the per-flag string params from the on-disk file. Empty list when the file is missing/unreadable, the flag is unknown, or the value did not come from the new structured shape (legacy bool-only flags carry no params by definition). """ return list(_read_params().get(flag_name, ())) def mqtt_tracked_methods() -> frozenset[str]: """Frozen set of method names whose status events should be enriched for MQTT tracing. Driven entirely by the server-side ``mqtt_tracked_methods`` flag's params list — the agent has no hard-coded list, so adding/removing tracked types is a server-side config change with no agent rollout. Cached: ``_read_state`` pre-builds the frozenset and invalidates it when the flags file's mtime changes. On the hot path — every Reportable message in ``the_sink._call_unlocked`` — this is a single ``os.stat`` syscall plus an identity-stable frozenset return. Two consecutive calls within the same mtime window return the same instance. """ _read_state() return _cached_mqtt_methods defence360agent/internals/geo.py0000644000000000000000000000462000000000000013673 0ustar from contextlib import contextmanager from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network from typing import Union from defence360agent.contracts.config import CountryInfo from defence360agent.utils.validate import IP class Reader: def __init__(self, geoip2_reader): self._geoip2_reader = geoip2_reader def get( self, address: Union[ str, IPv4Address, IPv4Network, IPv6Address, IPv6Network ], ): """ Returns geo country information from max mind's db request :param address: ip or network address e.g. '4.4.4.4, 1.2.0.0/16, 2001:678:4c::/48' :return: maxmind's geo info """ from geoip2.errors import AddressNotFoundError try: ip = IP.adopt_to_ipvX_network(address) except ValueError: return None try: obj = self._geoip2_reader.country(str(ip.network_address)) except AddressNotFoundError: return None # According to documentation: # https://geoip2.readthedocs.io/en/latest/#what-data-is-returned # (...) MaxMind does not always have every piece of data for any given # IP address. Because of these factors, it is possible for any request # to return a record where some or all of the attributes are # unpopulated. (...) return obj.country if obj else None def get_id( self, address: Union[ str, IPv4Address, IPv4Network, IPv6Address, IPv6Network ], ): """ :param address: valid ipv4 address :return: maxmind's id of the country """ country_info = self.get(address) if country_info: return country_info.geoname_id return None def get_code( self, address: Union[ str, IPv4Address, IPv4Network, IPv6Address, IPv6Network ], ): """ :param address: valid ipv4 address :return: country code in ISO-3166 format """ country_info = self.get(address) if country_info: return country_info.iso_code return None @contextmanager def reader(): """ :return Reader obj: instance to be reused to it's method calls """ import geoip2.database with geoip2.database.Reader(CountryInfo.DB) as geoip2_reader: yield Reader(geoip2_reader) defence360agent/internals/global_scope.py0000644000000000000000000000071600000000000015554 0ustar import logging logger = logging.getLogger(__name__) class GlobalScope(dict): def __getattr__(self, item): try: return self[item] except KeyError as err: raise AttributeError(f"{item} is not in global scope") from err def __setattr__(self, key, value): if key in self: logger.warning("Name %s is already in global scope", key) else: self[key] = value g = GlobalScope() defence360agent/internals/iaid.py0000644000000000000000000003450600000000000014035 0ustar import asyncio import grp import json import os import random import time from dataclasses import dataclass from logging import getLogger from pathlib import Path from typing import Callable from urllib.parse import urljoin from urllib.request import Request from defence360agent.api.server import API, APIError from defence360agent.contracts.license import LicenseCLN from defence360agent.utils import atomic_rewrite, safe_cancel_task from defence360agent.utils.common import DAY from defence360agent.internals.global_scope import g from defence360agent.internals.deadlock_detecting_lock import ( DeadlockDetectingLock, DeadlockError, ) logger = getLogger(__name__) _MAX_TRIES = 10 _TIMEOUT_MULTIPLICATOR = 2 """ >>> _MAX_TRIES_FOR_DOWNLOAD = 10 >>> _TIMEOUT_MULTIPLICATOR = 2 >>> [(1 << i) * _TIMEOUT_MULTIPLICATOR for i in range(1, _MAX_TRIES_FOR_DOWNLOAD)] # noqa [4, 8, 16, 32, 64, 128, 256, 512, 1024] """ _ACTIVATE_MINIMUM_TIMEOUT = 60 class IAIDTokenError(RuntimeError): """Can't get iaid token for any reason.""" class IndependentAgentIDAPI(API): API_PATH = "/api/auth/agent/{}" REGISTER_URL = urljoin(API._BASE_URL, API_PATH.format("register")) ACTIVATE_URL = urljoin(API._BASE_URL, API_PATH.format("activate")) LOGIN_URL = urljoin(API._BASE_URL, API_PATH.format("login")) TOKEN_INFO = urljoin(API._BASE_URL, API_PATH.format("token-info")) IAID_DIR = Path("/var/imunify360") IAID_FILE = IAID_DIR / "iaid" IAID_PASSWORD_FILE = IAID_DIR / "iaid-password" IAID_TOKEN_FILE = IAID_DIR / "iaid-token" IAID_ACTIVATED_FILE = IAID_DIR / "iaid-activated" _tasks = { "register": [], "activate": [], "login": [], } _register_lock = DeadlockDetectingLock() _activate_lock = asyncio.Lock() @dataclass(frozen=True) class TokenInfo: __slots__ = [ "valid", "iaid", "license_status", "server_id", "need_renew", ] valid: bool iaid: str license_status: str # "ok", "ok-av", "ok-avp", "ok-trial" server_id: str need_renew: bool @staticmethod async def _retry_on_error(coro: Callable, *args, attempt, timeout=0): # Exponential backoff retry await asyncio.sleep( timeout + random.randrange(1 << attempt) * _TIMEOUT_MULTIPLICATOR ) await coro(*args) @classmethod def _add_task(cls, type, coro: Callable, *args, attempt, timeout=0): cls._tasks[type] = [ task for task in cls._tasks[type] if not task.done() ] if len(cls._tasks[type]) <= 1: loop = asyncio.get_event_loop() cls._tasks[type].append( loop.create_task( cls._retry_on_error( coro, *args, attempt=attempt, timeout=timeout ) ) ) else: logger.info("Task %s already in retry queue", type) @classmethod def add_initial_task(cls): cls._add_task("activate", cls.activate, attempt=0) @classmethod async def shutdown(cls): for type, tasks in cls._tasks.items(): for task in tasks: if not task.done(): await safe_cancel_task(task) logger.info("Retry task %s was canceled.", type) @staticmethod def _gid(): return grp.getgrnam("_imunify").gr_gid @classmethod def get_iaid(cls): if cls.IAID_FILE.exists(): return cls.IAID_FILE.read_text() return None @staticmethod def _request(url, headers=None, method="POST", **kwargs): _headers = {"Content-Type": "application/json"} if headers is not None: _headers.update(headers) return Request( url, method=method, headers=_headers, data=json.dumps(kwargs).encode() if kwargs else None, ) @classmethod def is_registered(cls): return all( iaid_file.exists() for iaid_file in (cls.IAID_FILE, cls.IAID_PASSWORD_FILE) ) @classmethod async def get_token(cls): """Ensure that iaid token is up to date Return iaid token or raise IAIDTokenError.""" if IndependentAgentIDAPI.is_token_expired(): await IndependentAgentIDAPI.login() if IndependentAgentIDAPI.is_token_expired(): raise IAIDTokenError("IAID token is expired") try: token = cls.IAID_TOKEN_FILE.read_text(encoding="ascii").strip() if not token: raise IAIDTokenError("IAID_TOKEN_FILE is empty") return token except Exception as e: raise IAIDTokenError(f"Can't get iaid token, reason: {e}") from e @classmethod async def _get_token_info(cls) -> TokenInfo: iaid_token = await cls.get_token() headers = {"X-Auth": iaid_token} request = cls._request(cls.TOKEN_INFO, headers=headers, method="GET") result = await cls.async_request(request) token = result.get("token_info") if token is None: raise APIError("wrong response %r", result) try: return cls.TokenInfo(**token) except TypeError as e: raise APIError("incomplete token_info %r: %s" % (token, e)) from e @classmethod def is_token_expired(cls): try: stat = os.stat(cls.IAID_TOKEN_FILE) except FileNotFoundError: st_mtime = 0.0 else: st_mtime = stat.st_mtime return time.time() - st_mtime > DAY @classmethod async def register(cls, force=False, tried_credentials_ts=None, attempt=1): # In case of Unauthorized 401 for login/activate, iaid initiates force registration. # Prevent multiple force registrations by checking if the lock was already acquired. # This approach also works if the lock was already acquired by non-force registration, # as the non-force registration is currently only triggered if iaid wasn't registered. was_waiting = cls._register_lock.locked() try: async with cls._register_lock: if cls.is_registered(): if not force or was_waiting: return # If credentials were already updated - no need to register again. # This check makes the above `was_waiting` check obsolete in most cases, # but some old filesystems have second precision of for a file mtime. # Both checks are kept to decrease a chance of race conditions. if ( tried_credentials_ts is not None and tried_credentials_ts < cls._get_credentials_ts() ): return payload = dict() server_id = LicenseCLN.get_server_id() if server_id: payload["server_id"] = server_id request = cls._request(cls.REGISTER_URL, **payload) try: result = await cls.async_request(request) cls.IAID_ACTIVATED_FILE.unlink(missing_ok=True) except APIError as e: logger.warning( "Something went wrong on register %r - attempt %s", e, attempt, ) if ( e.status_code is None or e.status_code >= 500 or e.status_code == 402 ) and attempt < _MAX_TRIES: # internal error we may try again cls._add_task( "register", cls.register, force, tried_credentials_ts, attempt + 1, attempt=attempt, ) else: logger.error( "Failed to register (%s) after %s attempts: %r", request.full_url, attempt, e, ) return else: atomic_rewrite( str(cls.IAID_FILE), result["iaid"], backup=cls.IAID_FILE.exists(), uid=-1, gid=cls._gid(), permissions=0o640, ) atomic_rewrite( str(cls.IAID_PASSWORD_FILE), result["password"], backup=cls.IAID_PASSWORD_FILE.exists(), permissions=0o600, ) await cls.activate() except DeadlockError: logger.error( "Received incorrect credentials on register after %s attempts", attempt, ) @classmethod async def ensure_is_activated_and_valid(cls): """Check whether the agent activated""" if not cls.IAID_ACTIVATED_FILE.exists(): await cls.activate() return lic = LicenseCLN.get_token() token = await cls._get_token_info() if token.license_status != lic.get( "status" ) or token.server_id != lic.get("id"): logger.error("Got a corrupted token: %r", token) await cls.reactivate() return iaid = cls.IAID_FILE.read_text() if not token.valid or token.iaid != iaid or token.need_renew: await cls.login() @classmethod def _get_credentials_ts(cls): return cls.IAID_PASSWORD_FILE.stat().st_mtime @classmethod async def activate(cls, attempt=1): if cls.IAID_ACTIVATED_FILE.exists(): g["iaid"] = cls.get_iaid() return if not cls.is_registered(): logger.warning("need to register first before activate") await cls.register() return if LicenseCLN.is_free(): g["iaid"] = cls.get_iaid() if cls.is_token_expired(): await cls.login() return lic = LicenseCLN.get_token() if not lic: logger.warning( "Can't continue iaid activation: no valid license is found" ) return async with cls._activate_lock: # A concurrent activate() may have already completed # while we were waiting for the lock. if cls.IAID_ACTIVATED_FILE.exists(): g["iaid"] = cls.get_iaid() return iaid = cls.IAID_FILE.read_text() password = cls.IAID_PASSWORD_FILE.read_text() credentials_ts = cls._get_credentials_ts() request = cls._request( cls.ACTIVATE_URL, iaid=iaid, password=password, license=lic ) g["iaid"] = iaid need_to_register = False try: await cls.async_request(request) cls.IAID_ACTIVATED_FILE.touch() except APIError as e: logger.warning( "Something went wrong on activate %r attempt %s", e, attempt, ) if e.status_code and e.status_code == 401: # need to register again, do it outside of lock need_to_register = True elif ( e.status_code and (e.status_code >= 500 or e.status_code == 402) and attempt < _MAX_TRIES ): # internal error we may try again # 402 - if it is fresh registration it may take # time to sync CLN db cls._add_task( "activate", cls.activate, attempt + 1, attempt=attempt, timeout=_ACTIVATE_MINIMUM_TIMEOUT, ) else: logger.error( "Failed to activate (%s) after %s attempts: %r", request.full_url, attempt, e, ) else: cls.IAID_TOKEN_FILE.unlink(missing_ok=True) await cls.login() if need_to_register: await cls.register(force=True, tried_credentials_ts=credentials_ts) @classmethod async def reactivate(cls): cls.IAID_ACTIVATED_FILE.unlink(missing_ok=True) await cls.activate() @classmethod async def login(cls, attempt=1): if not cls.is_registered(): logger.error("need to register first before login") return iaid = cls.IAID_FILE.read_text() password = cls.IAID_PASSWORD_FILE.read_text() credentials_ts = cls._get_credentials_ts() request = cls._request(cls.LOGIN_URL, iaid=iaid, password=password) try: result = await cls.async_request(request) except APIError as e: logger.warning( "Something wrong happened on login %r attempt %s", e, attempt ) if attempt < _MAX_TRIES: if e.status_code is None or e.status_code >= 500: # internal error we may try again cls._add_task( "login", cls.login, attempt + 1, attempt=attempt ) elif e.status_code == 401: await cls.register( force=True, tried_credentials_ts=credentials_ts ) else: logger.error( "Failed to login (%s) after %s attempts: %r", request.full_url, attempt, e, ) else: atomic_rewrite( str(cls.IAID_TOKEN_FILE), result["token"], backup=cls.IAID_TOKEN_FILE.exists(), uid=-1, gid=cls._gid(), permissions=0o640, ) defence360agent/internals/lazy_load.py0000644000000000000000000000030300000000000015071 0ustar class CoreSource: MESSAGES = ("defence360agent.contracts.messages",) ENDPOINTS = ( "defence360agent.simple_rpc", "defence360agent.feature_management.rpc.endpoints", ) defence360agent/internals/logger.py0000644000000000000000000003704200000000000014404 0ustar import getpass import logging import logging.config import logging.handlers import os import sys import time import traceback import uuid from contextlib import contextmanager, suppress from functools import lru_cache import sentry_sdk import yaml from defence360agent.contracts import config, sentry from defence360agent.contracts.config import AcronisBackup from defence360agent.contracts.config import Logger as Config from defence360agent.contracts.config import Sentry from defence360agent.utils import antivirus_mode, is_root_user from defence360agent.application import tags PREFIX = os.environ.get("IMUNIFY360_LOGGING_PREFIX", "") logger = logging.getLogger(__name__) def _sentry_init(debug=False): # if config invalid, we still need to be able to configure logging try: error_reporting = Sentry.ENABLE except (KeyError, AssertionError): error_reporting = True if error_reporting: sentry_sdk.init( dsn=Sentry.DSN, debug=debug, release=config.Core.VERSION, attach_stacktrace="on", ) with sentry_sdk.configure_scope() as scope: for tag, value in sentry.tags().items(): scope.set_tag(tag, value) scope.user = {"id": sentry.tag("server_id")} return { "level": "ERROR", "class": "sentry_sdk.integrations.logging.SentryHandler", } else: return { "level": "NOTSET", "class": "logging.NullHandler", } class _LoggerDynConfig: _ROOT_LOG_DIR = "/var/log/%s" % config.Core.PRODUCT @staticmethod def _user_log_dir(): return "/var/log/%s_user_logs/%s" % ( config.Core.PRODUCT, getpass.getuser() or os.getuid(), ) def __init__(self): is_root = is_root_user() self.log_dir = self._ROOT_LOG_DIR if is_root else self._user_log_dir() self.mutableDictConfig = { "loggers": { "network": { "level": "DEBUG", # network_log is disabled by default' "handlers": [], }, "defence360agent.internals.the_sink": { "level": "DEBUG", # process_message_log is disabled by default' "handlers": [], }, "event_hook": { "level": "INFO", "handlers": [], }, }, "version": 1, "handlers": { "sentry": _sentry_init(), "error_log": { "level": "WARNING", "formatter": "abstimestamp", "filename": "%s/error.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, "network_log": { "level": "DEBUG", "formatter": "abstimestamp", "filename": "%s/network.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, "debug_log": { "level": "DEBUG", "formatter": "abstimestamp", "filename": "%s/debug.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, "console_log": { "level": "INFO", "formatter": "abstimestamp", "filename": "%s/console.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, "hook_log": { "level": "INFO", "formatter": "eventhook", "filename": "%s/hook.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, "console": { "formatter": "abstimestamp", "class": "logging.StreamHandler", "stream": "ext://sys.stderr", "level": "INFO", }, "process_message_log": { "formatter": "reltimestamp", # DEF-26794: append mode (default). With logrotate's # copytruncate, mode="w" would leave the fd offset past # EOF after truncation and re-inflate the file with # sparse zeros. O_APPEND seeks to the (now-zero) end # before each write, so the file size resets cleanly. "level": "DEBUG", "filename": "%s/process_message.log" % self.log_dir, "class": "logging.FileHandler", "encoding": "utf8", }, }, "root": { "level": "NOTSET", "handlers": [ "console_log", # 'debug_log' is disabled by default, "error_log", "sentry", ], }, "mkdir": "logs", "formatters": { "reltimestamp": { "format": ( "%(levelname)-7s [+%(relativeCreated)5dms] " f"{PREFIX}%(name)50s|%(message)s" ) }, "abstimestamp": { "format": ( f"%(levelname)-7s [%(asctime)s] {PREFIX}%(name)s:" " %(message)s" ) }, "eventhook": {"format": "%(created)d : %(message)s"}, }, "disable_existing_loggers": False, } self.mutableDictConfig["loggers"]["AcronisClientInstaller"] = { "level": "INFO", "handlers": [], } self.mutableDictConfig["handlers"]["acronis_installer_log"] = { "formatter": "abstimestamp", # DEF-26794: append mode (default). See process_message_log # comment above for why mode="w" is unsafe with copytruncate. "level": "INFO", "filename": os.path.join(self.log_dir, AcronisBackup.LOG_NAME), "class": "logging.FileHandler", "encoding": "utf8", } if not is_root: # The per-user log dir is owned by the unprivileged user, so root's # logrotate must not rotate it (it would let the user redirect # root's create/copy/truncate via a symlink). Bound these logs # in-process instead — as the owning user — mirroring the size # policy logrotate applies to the root logs. for handler in self.mutableDictConfig["handlers"].values(): if handler.get("class") == "logging.FileHandler": handler["class"] = "logging.handlers.RotatingFileHandler" handler["maxBytes"] = Config.MAX_LOG_FILE_SIZE handler["backupCount"] = Config.BACKUP_COUNT @lru_cache(1) def _late_init(): return _LoggerDynConfig() def _we_are_in_cagefs(): """ :return bool: True if python interpreter is being run in CageFS container, otherwise False :raise: never Current implementation simply checks "/var/.cagefs" presence, as Anton Volkov consulted us to do. Placing this function not in 'subsys' package, because 'logger' module is one of cornerstones dependency for 'subsys' package as well. """ with suppress(OSError): return os.path.exists("/var/.cagefs") def _chmod_log_dirs(dirname, dir_perm, file_perm): """Change file/dir modes recursively. Starting at dirname, change all inner directory permissions to dir_perm, file permissions to file_perm Permission errors are logged to stderr and are ignored in any case. """ def _os_chmod(file_dir_path, permission): try: os.chmod(file_dir_path, permission) except PermissionError as e: sys.stderr.write( "[WARNING] cannot chmod on {}: {}".format(file_dir_path, e) ) _os_chmod(dirname, dir_perm) for path, dirs, files in os.walk(dirname): for directory in dirs: _os_chmod(os.path.join(path, directory), dir_perm) for name in files: _os_chmod(os.path.join(path, name), file_perm) def reconfigure(): """ Re-catch with _LoggerDynConfig and re-open log files """ if os.getenv("IMUNIFY360_DISABLE_LOGGING"): pass else: try: # Set sentry.TAGS from saved file tags.cached_fill() log_dir = _late_init().log_dir os.makedirs(log_dir, Config.LOG_DIR_PERM, exist_ok=True) _chmod_log_dirs(log_dir, Config.LOG_DIR_PERM, Config.LOG_FILE_PERM) logging.config.dictConfig(_late_init().mutableDictConfig) except OSError: # We do not create user logs to keep user isolation # level high. # # Another alternative is # cagefs.mp:%/var/log/imunify360_user_log # but it is not working for some reason, we need to find out # later why. if not _we_are_in_cagefs(): traceback.print_exc(file=sys.stderr) sys.stderr.write( "%s logger is not available.\n" % config.Core.PRODUCT ) except Exception: # be robust: do not die if dictConfig fails traceback.print_exc(file=sys.stderr) sys.stderr.write( "%s logger is not available.\n" % config.Core.PRODUCT ) else: # logging is configured successfully sys.excepthook = _log_uncaught_exceptions def _log_uncaught_exceptions(exc_type, exc_value, exc_traceback): if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback) return logger.critical( "uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) ) def update_logging_config_from_file(filename): with open(filename) as config_file: config = yaml.safe_load(config_file) _late_init().mutableDictConfig.update(config) reconfigure() def get_fds(): handlers = logging.root.handlers for _logger in _late_init().mutableDictConfig["loggers"].keys(): handlers.extend(logging.getLogger(_logger).handlers) return [ h.stream for h in handlers if hasattr(h, "stream") and hasattr(h.stream, "fileno") and h.stream != sys.stderr ] def get_log_file_names(): return [ values["filename"] for _, values in _late_init().mutableDictConfig["handlers"].items() if "filename" in values ] def getNetworkLogger(name): if name in sys.modules: return logging.getLogger("network." + sys.modules[name].__name__) else: return logging.getLogger("network." + name) # NOTE: client expects that this function will return # the same value always - /var/log/imunify360. They base their logrotate # configs on this value. In case of some updates, corresponding teams # should be notified before update to update their logrotate configs. def log_dir() -> str: """ Return base log directory for the product. Supposed to be used by clients to build the path to their own logs. """ return _late_init().log_dir def setLogLevel(verbose): # FIXME if antivirus_mode.disabled: _late_init().mutableDictConfig["loggers"]["AcronisClientInstaller"][ "handlers" ].append("acronis_installer_log") if verbose >= 2: _late_init().mutableDictConfig["loggers"]["network"][ "handlers" ].append("network_log") if verbose >= 3: _late_init().mutableDictConfig["loggers"][ "defence360agent.internals.the_sink" ]["handlers"].append("process_message_log") if verbose >= 4: _late_init().mutableDictConfig["root"]["handlers"].append("debug_log") _late_init().mutableDictConfig["loggers"]["event_hook"]["handlers"].append( "hook_log" ) reconfigure() def setConsoleLogLevel(newloglevel): """ also results in reconfigure() """ _late_init().mutableDictConfig["handlers"]["console"][ "level" ] = newloglevel reconfigure() # openAibolitActionsLog and openMdsActionsLog are deprecated and should be removed # after release of https://gerrit.cloudlinux.com/c/defence360/+/225868 @contextmanager def openAibolitActionsLog(scan_id: str): path = os.path.join(_late_init().log_dir, "aibolit_actions.log") with open(path, "a") as f: f.write(f'{time.strftime("%Y-%m-%d %H:%M:%S")} | {scan_id} | ') yield f f.write("\n\n") # openAibolitActionsLog and openMdsActionsLog are deprecated and should be removed # after release of https://gerrit.cloudlinux.com/c/defence360/+/225868 @contextmanager def openMdsActionsLog(scan_id: str): log_dir = _late_init().log_dir os.makedirs(log_dir, exist_ok=True) path = os.path.join(log_dir, "mds_actions.log") with open(path, "a") as f: f.write(f'{time.strftime("%Y-%m-%d %H:%M:%S")} | {scan_id} | ') yield f f.write("\n\n") class EventHookLogger: class _EventLogger: class _HookLogger: tpl = ( "{uuid:s} : {action:s} {native:s}: " "{event:s} : {subtype:s} : {path:s}" ) def __init__(self, parent, path, native): self.path = path self.event = parent.event self.subtype = parent.subtype self.uuid = parent.uuid self.log = parent.log self.native = native def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def _log(self, action, message=""): data = { "uuid": str(self.uuid), "action": action, "native": "native " if self.native else "", "event": self.event, "subtype": self.subtype, "path": self.path, } msg = self.tpl.format(**data) if message: msg = " : ".join([msg, message]) self.log(msg) def begin(self): self._log("started") def finish(self, exit_code, err): message = "OK" if exit_code == 0 else "ERROR" if exit_code: message = ":".join([message, str(exit_code)]) if err: if isinstance(err, bytes): err = err.decode(errors="backslashreplace") message = "\n".join([message, err]) self._log("done", message) def __init__(self, parent, event, subtype): self.event = event self.subtype = subtype self.uuid = uuid.uuid4() self.log = parent.log def __call__(self, path, native=False): return self._HookLogger(self, path, native=native) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): pass def __init__(self): logger = logging.getLogger("event_hook") self.log = logger.info def __call__(self, event, subtype): return self._EventLogger(self, event, subtype) defence360agent/internals/logging_protocol.py0000644000000000000000000000210500000000000016464 0ustar import asyncio class LoggingProtocol(asyncio.Protocol): def __init__(self, logger, network_logger, real_protocol): self._logger = logger self._network_logger = network_logger self._real_protocol = real_protocol def connection_made(self, transport): self._network_logger.debug("Connection made.") self._handle(lambda: self._real_protocol.connection_made(transport)) def connection_lost(self, exc): self._network_logger.debug("Connection lost.") self._handle(lambda: self._real_protocol.connection_lost(exc)) def datagram_received(self, data, addr): self._network_logger.debug("datagram_received: {!r}".format(data)) self._handle(lambda: self._real_protocol.datagram_received(data, addr)) def data_received(self, data): self._network_logger.debug("data_received: {!r}".format(data)) self._handle(lambda: self._real_protocol.data_received(data)) def _handle(self, impl): try: impl() except Exception as e: self._logger.exception(str(e)) defence360agent/internals/message_status_publisher.py0000644000000000000000000001452200000000000020227 0ustar """ Lightweight message status publisher for asyncclient. Publishes MESSAGE_STATUS events to the local proxy which relays them to the EMQX broker via MQTT. Each call to report() submits an HTTP POST to a thread pool — no batching or internal queue. Usage:: publisher = MessageStatusPublisher() message_id_gen = Gen() msg = {...} message_id_gen.enrich(msg) # adds message_reporter_id / message_reporter_increment publisher.report(msg, reporter_id_gen) """ import atexit import concurrent.futures import json import logging import os import threading import time import urllib.error import urllib.request import uuid from defence360agent.internals.feature_flags import ( MESSAGE_LOSS_OBSERVABILITY_FLAG, is_enabled, ) logger = logging.getLogger(__name__) _IAID_PATH = "/var/imunify360/iaid" _PROXY_URL = os.environ.get("IMUNIFY_PROXY_URL", "http://127.0.0.1:11234") _PUBLISH_ENDPOINT = _PROXY_URL.rstrip("/") + "/api/v1/mqtt-publish" # Shared secret for proxy APIKey middleware; must match # IMUNIFY_PROXY_API_KEY on the proxy side (see src/proxy/auth/jwt.go). # When unset (e.g. in tests or pre-deploy) the proxy logs a WARN and # passes requests through. _PROXY_API_KEY = os.environ.get("IMUNIFY_PROXY_API_KEY", "") _POST_TIMEOUT = 5 _MAX_WORKERS = 4 # Cap on concurrently queued+in-flight POSTs. Matches the Go publisher's # statusPublisherQueueSize; when the broker or proxy is slow we prefer # dropping new events over unbounded memory growth. _MAX_INFLIGHT = 128 class Gen: """ID + monotonic counter generator. Each instance has its own UUID and its own counter. """ def __init__(self) -> None: self.id = uuid.uuid4().hex self._counter = 0 self._lock = threading.Lock() def _next(self) -> int: with self._lock: value = self._counter self._counter += 1 return value def enrich(self, msg: dict) -> None: """Add message_reporter_id and message_reporter_increment to msg.""" msg["message_reporter_id"] = self.id msg["message_reporter_increment"] = self._next() def _read_iaid() -> str: try: with open(_IAID_PATH) as f: return f.read().strip() except OSError: return "" class MessageStatusPublisher: def __init__(self) -> None: self._iaid: str = "" self._init_lock = threading.Lock() self._initialized = False self._pool = concurrent.futures.ThreadPoolExecutor( max_workers=_MAX_WORKERS, thread_name_prefix="msg-status", ) self._inflight = threading.BoundedSemaphore(_MAX_INFLIGHT) self._dropped = 0 self._dropped_lock = threading.Lock() def pop_dropped(self) -> int: """Drops since the last call, then reset (delta for metrics).""" with self._dropped_lock: dropped, self._dropped = self._dropped, 0 return dropped def queue_depth(self) -> int: """In-flight reports awaiting completion (gauge, 0..cap).""" sem = self._inflight return max(0, sem._initial_value - sem._value) def _ensure_initialized(self) -> None: if self._initialized: return with self._init_lock: if self._initialized: return self._iaid = _read_iaid() if not self._iaid: logger.info( "msg-status: iaid not available yet (file %s missing or" " empty), will retry", _IAID_PATH, ) return self._initialized = True def report(self, msg: dict, reporter_gen: Gen, stage: str) -> None: """Publish a status record via HTTP POST to the proxy.""" # Gate the feature flag before any allocation: report() is called # per message and when tracking is disabled (default) we want zero # dict/pool overhead. if not is_enabled("mqtt_tracking"): return method = msg.get("method", "") if not msg.get("message_reporter_id"): return # Bounded queue: drop new events when we're already at capacity so # a slow proxy/broker can't grow our memory unboundedly. Mirrors # the Go publisher's fire-and-drop channel pattern. if not self._inflight.acquire(blocking=False): if is_enabled(MESSAGE_LOSS_OBSERVABILITY_FLAG): with self._dropped_lock: self._dropped += 1 logger.warning( "msg-status: queue full, dropping stage=%s method=%s", stage, method, ) return record = { "timestamp": time.time(), "reporter_id": reporter_gen.id, "reporter_increment": reporter_gen._next(), "message_reporter_id": msg.get("message_reporter_id", ""), "message_reporter_increment": msg.get( "message_reporter_increment", 0 ), "message_type": method, "stage": stage, } try: future = self._pool.submit(self._do_post, record, stage, method) except RuntimeError: # Pool already shut down. self._inflight.release() return future.add_done_callback(lambda _: self._inflight.release()) def _do_post(self, record: dict, stage: str, method: str) -> None: self._ensure_initialized() if not self._iaid: return record["iaid"] = self._iaid try: payload = json.dumps(record).encode() headers = {"Content-Type": "application/json"} if _PROXY_API_KEY: headers["X-API-Key"] = _PROXY_API_KEY req = urllib.request.Request( _PUBLISH_ENDPOINT, data=payload, headers=headers, method="POST", ) with urllib.request.urlopen(req, timeout=_POST_TIMEOUT) as resp: resp.read() except Exception as e: logger.warning( "msg-status: POST failed stage=%s method=%s: %r", stage, method, e, ) def shutdown(self) -> None: self._pool.shutdown(wait=False) publisher = MessageStatusPublisher() atexit.register(publisher.shutdown) message_id_gen = Gen() defence360agent/internals/persistent_message.py0000644000000000000000000001044200000000000017024 0ustar import time from logging import getLogger from defence360agent.internals.feature_flags import ( MESSAGE_LOSS_OBSERVABILITY_FLAG, is_enabled, ) from defence360agent.model.instance import db from defence360agent.model.messages_to_send import MessageToSend logger = getLogger(__name__) class PersistentMessagesQueue: """ The queue to store messages sent to the server if it is unavailable. - stores more recent data; if a limit is exceeded, older messages are deleted. - no duplicate messages are sent NOTE: it is worth remembering that when writing a large number of messages, the amount of memory used may increase by the size of the sqlite cache (this may not be immediately obvious). https://www.sqlite.org/pragma.html#pragma_cache_size """ def __init__(self, buffer_limit=20, storage_limit=1000, model=None): self._buffer_limit = buffer_limit self._storage_limit = storage_limit self._buffer = [] # [(timestamp, message),...] self._model = model or MessageToSend self.dropped_total = 0 self._evicted = 0 def pop_evicted(self) -> int: """Evictions since the last call, then reset (delta for metrics).""" evicted, self._evicted = self._evicted, 0 return evicted def push_buffer_to_storage(self) -> None: if self._buffer: with db.atomic(): # buffer may contain older messages than db, # so remove oldest items after insert self._model.insert_many(self._buffer) need_to_remove = self.storage_size - self._storage_limit if need_to_remove > 0: # keep only the most recent messages removed = self._model.delete_old(need_to_remove) # This is the last point at which the messages exist, so it # is the only place their loss can be reported. self.dropped_total += removed if is_enabled(MESSAGE_LOSS_OBSERVABILITY_FLAG): self._evicted += removed logger.warning( "Persistent message queue overflow: dropped %d oldest" " message(s), storage_limit=%d, dropped_total=%d", removed, self._storage_limit, self.dropped_total, ) self._buffer = [] def pop_all(self) -> list: items = [] with db.atomic(): items += list( self._model.select( self._model.timestamp, self._model.message ).tuples() ) self._model.delete().execute() items += self._buffer self._buffer = [] return sorted(items) # older first def peek_stored(self) -> list: """Return stored rows as (id, timestamp, message) oldest-first without deleting (buffer is neither flushed nor included).""" return list(self._model.get_all_ordered().tuples()) def drain_buffer(self) -> list: """Return and clear the in-memory buffer as (timestamp, message).""" items, self._buffer = self._buffer, [] return items def delete(self, ids: list) -> None: if ids: with db.atomic(): self._model.delete_in(ids) def update_message(self, message_id: int, message: bytes) -> None: with db.atomic(): self._model.set_message(message_id, message) def empty(self) -> bool: return self.qsize() == 0 def qsize(self) -> int: return self.storage_size + len(self._buffer) @property def buffer_size(self) -> int: return len(self._buffer) @property def storage_size(self) -> int: return self._model.select().count() def put(self, message: bytes, timestamp=None): if timestamp is None: timestamp = time.time() self._buffer.append((timestamp, message)) if self.buffer_size >= self._buffer_limit: self.push_buffer_to_storage() def put_many(self, messages: list[tuple[float, bytes]]) -> None: self._buffer.extend(messages) if self.buffer_size >= self._buffer_limit: self.push_buffer_to_storage() defence360agent/internals/the_sink.py0000644000000000000000000003013200000000000014722 0ustar import asyncio import collections import io import reprlib import time import weakref import logging from operator import attrgetter from defence360agent.contracts.messages import Message, Reject from defence360agent.contracts.plugins import BaseMessageProcessor from defence360agent.internals.feature_flags import ( is_enabled, mqtt_tracked_methods, ) from defence360agent.internals.message_status_publisher import ( Gen, message_id_gen, publisher, ) from defence360agent.utils import safe_cancel_task from defence360agent.utils.common import DAY, ServiceBase, rate_limit from defence360agent.internals.global_scope import g logger = logging.getLogger(__name__) _reporter_gen_sink = Gen() ProcessingMessage = collections.namedtuple( "ProcessingMessage", ["message", "start_time"] ) class TheSink(BaseMessageProcessor): def __init__(self, sink_list, loop): self._sinks_ordered = sorted( sink_list, key=attrgetter("PROCESSING_ORDER") ) self._loop = loop self._task_manager = TaskManager( loop, MessageProcessor(self._sinks_ordered) ) g.sink = self def __repr__(self): return "%s.%s" % (self.__class__.__module__, self.__class__.__name__) def decompose(self, classobj): """ introspection: decompose a specific role :return classobj: instance or None """ options = [ sink for sink in self._sinks_ordered if isinstance(sink, classobj) ] assert len(options) <= 1, "Ambiguous request" return next(iter(options), None) def start(self): """ Make sure to run message processing bus only when every MessageSource (or MessageSource+MessageSink mix) got initialized """ self._task_manager.start() async def shutdown(self): logger.info("shutdown the sink started") self._task_manager.should_stop() logger.info("wait for current tasks") await self._task_manager.wait_current_tasks(timeout=5) logger.info("finish wait task") await self._task_manager.wait() async def process_message(self, message): await self._task_manager.push_msg(message) class TaskManager(ServiceBase): # max queue message size MAXSIZE = 100000 # number of concurrently processed messages CONCURRENCY = 5 # how long an individual message may be processed TIMEOUT = 3600 # seconds def __init__(self, loop, msg_processor): super().__init__(loop) self._queue = MessageQueue(maxsize=self.MAXSIZE) self._concurrency = self.CONCURRENCY self._process_message_timeout = self.TIMEOUT self._msg_processor = msg_processor self.tasks = weakref.WeakSet() self._throttled_logger = rate_limit(period=DAY, on_drop=logger.warning) self.throttled_log_error = self._throttled_logger(logger.error) async def push_msg(self, msg): """Push message unless the queue is full.""" if not self._queue.full(): await self._queue.put(MessageComparable(msg)) else: if self._throttled_logger.should_be_called: # send to Sentry args = ( ( "Message queue is full %s. " "Current processing messages: %s. Message ignored: %s" ), self._queue, self.current_processing_messages, msg, ) else: # don't serialize the queue on each log warning entry args = ( ( "Message queue is full. Queue size: %s " "Current processing messages: %s. Message ignored: %s" ), self._queue.qsize(), self.current_processing_messages, msg, ) self.throttled_log_error(*args) @property def current_processing_messages(self): # the loop should be safe (no ref should be removed from the weak # set while iterating) # https://stackoverflow.com/questions/12428026/safely-iterating-over-weakkeydictionary-and-weakvaluedictionary # noqa # the loop is constant time because # len(self.tasks) == self._concurrency (fixed & small) return tuple( ( task.processing_msg.message, round(time.monotonic() - task.processing_msg.start_time, 4), ) for task in self.tasks if not task.done() ) async def wait_current_tasks(self, timeout=None): if self.tasks: msg_to_process = [ (m.get("method"), m.get("message_id"), lasting) for m, lasting in self.current_processing_messages ] logger.info( "Waiting for %r processing to finish", msg_to_process, ) await asyncio.wait(self.tasks, timeout=timeout) async def _run(self): semaphore = asyncio.BoundedSemaphore(self._concurrency) try: while not self._should_stop: logger.debug("Message queue size: %s", self._queue.qsize()) try: await self.__limit_concurrency(semaphore) msg_comparable = await self._queue.get() except asyncio.CancelledError: break t = self._loop.create_task( self._msg_processor(msg_comparable.msg) ) # type: asyncio.Task t.processing_msg = ProcessingMessage( msg_comparable.msg, time.monotonic() ) t.add_done_callback(lambda _: semaphore.release()) t.add_done_callback(self._on_msg_processed) self.tasks.add(t) unprocessed = self._queue.qsize() if unprocessed: logger.warning( "There is still %s unprocessed messages in the queue", self._queue.qsize(), ) except: # NOQA logger.exception("Error during message processing:") async def __limit_concurrency(self, semaphore): """Try to acquire *semaphore* in a loop, log error on timeout.""" while True: try: return await asyncio.wait_for( semaphore.acquire(), timeout=self._process_message_timeout, ) except asyncio.TimeoutError: self.throttled_log_error( "Message hasn't been processed in %s seconds", self._process_message_timeout, ) @staticmethod def _on_msg_processed(future): e = future.exception() if e: logger.exception("Error during message processing:", exc_info=e) async def cancel_task(task): if not task.done(): await safe_cancel_task(task) class MessageProcessor(object): TIMEOUT_TO_SINK_PROCESS = 3600 def __init__(self, sinks): self.sinks = sinks self.locks = weakref.WeakValueDictionary() self.throttled_log_error = rate_limit(period=60 * 60)( logger.error ) # send event to Sentry once an hour async def __call__(self, msg): ip = msg.get("attackers_ip") if ip: lock = self.locks.setdefault(ip, asyncio.Lock()) async with lock: await self._call_unlocked(msg) else: await self._call_unlocked(msg) async def _call_unlocked(self, msg): # MQTT message-status tracing chokepoint. Enrich first so every # downstream stage (sink-received → queued → sending → sent) carries # the same message_reporter_id; otherwise sink-received fires before # the id exists and gets silently dropped by publisher.report's # missing-id guard. Two gates, both required: the master kill-switch # and the server-driven per-method allow-list. Adding a new tracked # type is a server-side config change — no agent rollout. if ( is_enabled("mqtt_tracking") and msg.get("method") in mqtt_tracked_methods() and "message_reporter_id" not in msg ): message_id_gen.enrich(msg) publisher.report(msg, _reporter_gen_sink, stage="agent-sink-received") start = time.monotonic() for sink in self.sinks: try: process_message_task = asyncio.create_task( sink.process_message(msg) ) processed = await asyncio.wait_for( # shielded only for debug DEF-18627, # it should intercept `CancelledError` that # `asyncio.wait_for` send to `process_message_task` # in case timeout asyncio.shield(process_message_task), timeout=self.TIMEOUT_TO_SINK_PROCESS, ) except asyncio.CancelledError: break except Reject as e: logger.info("Rejected: %s -> %r", str(e), msg) return except asyncio.TimeoutError: # debug for DEF-18627, it's supposed that during this exception # handling we will get call stack in logs and see last await # that hang out coroutine was made, # may be it's give us some hint about problem stack = io.StringIO() process_message_task.print_stack(file=stack) stack.seek(0) logger.error( "Message %r was not processed in the %r plugin in %ss; " "Traceback: %s", msg, sink, self.TIMEOUT_TO_SINK_PROCESS, stack.read(), ) return except Exception: logger.exception("Error processing %r in %r", msg, sink) return else: if isinstance(processed, Message): msg = processed finally: await cancel_task(process_message_task) processing_time = time.monotonic() - start logger.info("%s processed in %.4f seconds", msg, processing_time) if processing_time > msg.PROCESSING_TIME_THRESHOLD: # send to Sentry self.throttled_log_error( "%s message took longer to process than expected " "(%.4f sec > %.4f sec)", msg, processing_time, msg.PROCESSING_TIME_THRESHOLD, ) class MessageComparable(object): """Wrapper to make message comparable.""" # needed to keep order index = -1 @staticmethod def __new__(cls, msg): cls.index += 1 rv = super().__new__(cls) rv.priority = msg.PRIORITY, cls.index rv.msg = msg return rv def __lt__(self, other): return self.priority.__lt__(other.priority) def __repr__(self): return "<{klass}({msg!r}), priority={priority}>".format( klass=self.__class__.__name__, msg=self.msg, priority=self.priority, ) class MessageQueue(asyncio.PriorityQueue): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._repr = reprlib.Repr() self._repr.maxstring = 50 self._repr.maxtuple = 2000 async def put(self, item: MessageComparable): return await super().put(item) def __str__(self): # NOTE: do not flood console.log with full queue msg_counts = sorted( collections.Counter( [item.msg.__class__.__qualname__ for item in self._queue] ).items(), key=lambda item: item[1], # sorted by number of messages reverse=True, ) return ( f"" ) defence360agent/migrate.py0000644000000000000000000001401500000000000012551 0ustar #!/opt/imunify360/venv/bin/python3 """This module import peewee_migrate and apply migrations, for Imunify-AV it's entrypoint for service""" import contextlib import os import sys import signal import threading import time from collections.abc import Iterable from logging import getLogger from peewee_migrate import migrator from playhouse.sqlite_ext import SqliteExtDatabase import defence360agent.internals.logger from defence360agent.application import app from defence360agent.application.settings import configure from defence360agent.contracts.config import Core from defence360agent.contracts.config import Model from defence360agent.router import Router from defence360agent.subsys import systemd_notifier from defence360agent.model.instance import db as db_instance from defence360agent.model import tls_check from defence360agent.utils import ( write_pid_file, IM360_RESIDENT_PID_PATH, cleanup_pid_file, ) from defence360agent.utils.check_db import ( recreate_schema_models, ) logger = getLogger(__name__) GO_SERVICE_NAME = "/usr/bin/imunify-resident" @contextlib.contextmanager def exc_handler(log_msg: str, reraise: bool): """ Logs error in case of exception. Depending on `reraise`: - re-raise exception and don't include exception info in the log operation - do not re-raise exception and include exception info in the log operation """ try: yield except Exception: logger.error(log_msg, exc_info=not reraise) if reraise: raise def apply_migrations(db: SqliteExtDatabase, migrations_dirs: Iterable[str]): """Apply migrations: restructure db, config files, etc.""" router = Router( db, migrations_dirs=migrations_dirs, logger=logger, ) # HACK: Migrator uses global unconfigurable LOGGER, # overrride it, to use our logging settings migrator.LOGGER = logger router.run() def prepare_databases( migrations_dirs: Iterable[str], attached_dbs: tuple[tuple[str, str], ...] = tuple(), ): """ Apply migrations and recreate attached databases. The workflow: 1. Apply migrations 2. Regardless whether the migrations were applied - recreate attached databases 3. If the recreation of the attached databases was successful - apply migrations again - this is done to verify that migrations will successfully apply in future for the recreated databases - the recreation + the migrations in this step are within the same transaction, so databases will only be recreated if the migrations can applied after the recreation. """ # prepare database to operate in WAL journal_mode and run migrations tls_check.reset() db_instance.init(Model.PATH) attached_schemas = [] for db_path, schema_name in attached_dbs: db_instance.execute_sql("ATTACH ? AS ?", (db_path, schema_name)) attached_schemas.append(schema_name) try: logger.info("Applying database migrations...") systemd_notifier.notify(systemd_notifier.AgentState.MIGRATING) with db_instance.atomic("EXCLUSIVE"), exc_handler( "Error applying migrations", reraise=False ): apply_migrations(db_instance, migrations_dirs) logger.info("Recreating attached databases...") with db_instance.atomic("EXCLUSIVE"), exc_handler( "Error recreating attached databases", reraise=True ): # Migration history is stored in main db, so to automatically recreate # attached dbs it is required to recreate schema for them from models recreate_schema_models(db_instance, attached_schemas) # verify migrations can be applied after the attached dbs recreation with exc_handler( "Error applying migrations after recreating attached" " databases", reraise=True, ): apply_migrations(db_instance, migrations_dirs) finally: # close connection immediately since later this process # will be replaced by execv db_instance.close() # required in case package manager or user sends signals while migrations are still running def signal_handler(sig, _): logger.warning("Received signal %s in signal_handler", sig) logger.warning( "waiting %d seconds so that migrations can finish", Core.SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECS, ) time.sleep(Core.SIGNAL_HANDLER_MIGRATION_TIMEOUT_SECS) logger.info("Exiting") sys.exit(0) def run(*, start_pkg="defence360agent", configure=configure): """Entry point for Imunify-AV service. Apply migrations, and then replace process with {start_pkg}.run module.""" for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): signal.signal(sig, signal_handler) try: if start_pkg == "im360.run_resident": write_pid_file(IM360_RESIDENT_PID_PATH) os.umask(Core.FILE_UMASK) configure() defence360agent.internals.logger.reconfigure() migration_thread = threading.Thread( target=prepare_databases, args=(app.MIGRATIONS_DIRS, app.MIGRATIONS_ATTACHED_DBS), ) migration_thread.start() migration_thread.join() systemd_notifier.notify(systemd_notifier.AgentState.READY) logger.info("Starting main process...") systemd_notifier.notify(systemd_notifier.AgentState.STARTING) if start_pkg == "im360.run_resident": Core.GO_FLAG_FILE.touch(exist_ok=True) logger.info("Run imunify-resident service") os.execv( GO_SERVICE_NAME, [ GO_SERVICE_NAME, ] + sys.argv[1:], ) else: os.execv( sys.executable, [sys.executable, "-m", "{}".format(start_pkg)] + sys.argv[1:], ) except Exception: if start_pkg == "im360.run_resident": cleanup_pid_file(IM360_RESIDENT_PID_PATH) if __name__ == "__main__": run() defence360agent/migrations/0000755000000000000000000000000000000000000012722 5ustar defence360agent/migrations/001_initial.py0000644000000000000000000000437000000000000015311 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw class Incident(pw.Model): id = pw.IntegerField(primary_key=True, null=True) plugin = pw.CharField(null=True) rule = pw.CharField(null=True) timestamp = pw.FloatField(null=True) retries = pw.IntegerField(null=True) severity = pw.IntegerField(null=True) name = pw.CharField(null=True) description = pw.CharField(null=True) abuser = pw.CharField(null=True) class Meta: db_table = "incident" class IPList(pw.Model): ip = pw.CharField(primary_key=True, null=False) listname = pw.CharField( null=False, constraints=[pw.Check("listname in ('WHITE','BLACK','GRAY')")], ) expiration = pw.IntegerField(default=0, null=True) class Meta: db_table = "iplist" class BlocklistHistory(pw.Model): id = pw.IntegerField(primary_key=True, null=True) plugin = pw.CharField(null=True) rule = pw.CharField(null=True) timestamp = pw.FloatField(null=True) ip = pw.CharField(null=True) class Meta: db_table = "blocklist_history" class LastSynclist(pw.Model): timestamp = pw.FloatField(primary_key=True, null=True) class Meta: db_table = "last_synclist" def migrate(migrator, database, fake=False, **kwargs): """In memory of former create_db() (RIP)""" migrator.create_model(Incident) migrator.create_model(IPList) migrator.create_model(BlocklistHistory) migrator.create_model(LastSynclist) def rollback(migrator, database, fake=False, **kwargs): """Nothing to rollback.""" defence360agent/migrations/002_infected_domain_list.py0000644000000000000000000000231300000000000020017 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw class InfectedDomainList(pw.Model): id = pw.IntegerField(primary_key=True) name = pw.CharField(null=False) threat_type = pw.CharField(null=False) timestamp = pw.FloatField() class Meta: db_table = "infected_domain_list" def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(InfectedDomainList) def rollback(migrator, database, fake=False, **kwargs): migrator.remove_model(InfectedDomainList) defence360agent/migrations/003_import_from_list.py0000644000000000000000000000244700000000000017255 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import time import peewee as pw from peewee import IntegerField def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_fields( IPList, imported_from=pw.CharField(null=True), ctime=IntegerField(null=True, default=lambda: int(time.time())), ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "imported_from", "created") defence360agent/migrations/004_add_username_to_infected_domain_list.py0000644000000000000000000000217300000000000023236 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): InfectedDomainList = migrator.orm["infected_domain_list"] migrator.add_fields(InfectedDomainList, username=pw.CharField(null=True)) def rollback(migrator, database, fake=False, **kwargs): InfectedDomainList = migrator.orm["infected_domain_list"] migrator.remove_fields(InfectedDomainList, "username") defence360agent/migrations/005_timeout_in_iplist.py0000644000000000000000000000217700000000000017427 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_fields(IPList, deep=pw.IntegerField(null=True)) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "deep") defence360agent/migrations/006_comment_in_plist.py0000644000000000000000000000220200000000000017220 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_fields(IPList, comment=pw.CharField(null=True)) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "comment") defence360agent/migrations/007_add_country_code_fields.py0000644000000000000000000000315200000000000020516 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw class Country(pw.Model): code = pw.CharField(max_length=2, primary_key=True, null=False) name = pw.CharField(null=False) class Meta: db_table = "country" def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] Incident = migrator.orm["incident"] migrator.create_model(Country) migrator.add_fields(IPList, country=pw.ForeignKeyField(Country, null=True)) migrator.add_fields( Incident, country=pw.ForeignKeyField(Country, null=True) ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] Incident = migrator.orm["incident"] migrator.remove_fields(IPList, "country") migrator.remove_fields(Incident, "country") migrator.remove_model(Country) defence360agent/migrations/008_fill_countries.py0000644000000000000000000000063600000000000016711 0ustar # Data migration, currently not actual # Earlier it creates data for Country, add link to Incident.country # and Iplist.country # Now it uses in the 0012 migration after fix the FK in the Country def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" pass def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/009_drop_blocklist_history.py0000644000000000000000000000257500000000000020470 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw class BlocklistHistory(pw.Model): id = pw.IntegerField(primary_key=True, null=True) plugin = pw.CharField(null=True) rule = pw.CharField(null=True) timestamp = pw.FloatField(null=True) ip = pw.CharField(null=True) class Meta: db_table = "blocklist_history" def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" BlocklistHistory = migrator.orm["blocklist_history"] migrator.remove_model(BlocklistHistory) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" migrator.create_model(BlocklistHistory) defence360agent/migrations/010_drop_country_entities.py0000644000000000000000000000241300000000000020307 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] Incident = migrator.orm["incident"] Country = migrator.orm["country"] migrator.drop_index(IPList, "country") migrator.drop_index(Incident, "country") migrator.remove_fields(IPList, "country") migrator.remove_fields(Incident, "country") migrator.remove_model(Country) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/011_create_new_country_entities.py0000644000000000000000000000525700000000000021471 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ from time import time import peewee as pw class Country(pw.Model): id = pw.CharField(primary_key=True, null=False) code = pw.CharField(max_length=2, unique=True, null=False) name = pw.CharField(null=False) class Meta: db_table = "country" class CountrySubnets(pw.Model): country = pw.ForeignKeyField(Country, null=False) # 255.255.255.255/32 - max 18 symbols ip_net = pw.CharField(max_length=18, null=False) class Meta: db_table = "country_subnets" class CountryList(pw.Model): # available list names WHITE = "WHITE" BLACK = "BLACK" IP_LISTS = (WHITE, BLACK) country = pw.ForeignKeyField(Country, primary_key=True, null=False) listname = pw.CharField( null=False, constraints=[pw.Check("listname in ('WHITE','BLACK')")] ) ctime = pw.IntegerField(null=True, default=lambda: int(time())) # are OK comment = pw.CharField(null=True) class Meta: db_table = "country_list" def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] Incident = migrator.orm["incident"] migrator.create_model(Country) migrator.add_fields(IPList, country=pw.ForeignKeyField(Country, null=True)) migrator.add_fields( Incident, country=pw.ForeignKeyField(Country, null=True) ) migrator.create_model(CountrySubnets) migrator.create_model(CountryList) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" Country = migrator.orm["country"] CountrySubnets = migrator.orm["country_subnets"] CountryList = migrator.orm["country_list"] IPList = migrator.orm["iplist"] Incident = migrator.orm["incident"] migrator.remove_fields(IPList, "country") migrator.remove_fields(Incident, "country") migrator.remove_model(CountrySubnets) migrator.remove_model(CountryList) migrator.remove_model(Country) defence360agent/migrations/012_fill_countries_and_subnets.py0000644000000000000000000000037400000000000021270 0ustar """Peewee migrations: :: UPD: migration not needed anymore, countries and subnets are loaded after files update. """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/013_add_indexes_to_iplist.py0000644000000000000000000000212300000000000020212 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_index(IPList, "listname") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] migrator.drop_index(IPList, "listname") defence360agent/migrations/014_add_malware_hits.py0000644000000000000000000000450700000000000017155 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ import peewee as pw class MalwareScan(pw.Model): class Meta: db_table = "malware_scans" scanid = pw.CharField(primary_key=True) started = pw.IntegerField(null=False) completed = pw.IntegerField(null=False) type = pw.CharField( null=False, constraints=[pw.Check("type in ('on-demand', 'realtime')")] ) path = pw.CharField(null=False) total_files = pw.IntegerField(null=False, default=0) class MalwareHit(pw.Model): class Meta: db_table = "malware_hits" id = pw.IntegerField(primary_key=True) scanid = pw.ForeignKeyField(MalwareScan, null=False) user = pw.CharField(null=False) orig_file = pw.CharField(null=False) type = pw.CharField(null=False) restored = pw.BooleanField(null=False, default=False) class MalwareIgnorePath(pw.Model): class Meta: db_table = "malware_ignore_path" path = pw.CharField(primary_key=True) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" migrator.create_model(MalwareScan) migrator.create_model(MalwareHit) migrator.create_model(MalwareIgnorePath) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" MalwareScan = migrator.orm["malware_scans"] MalwareHit = migrator.orm["malware_hits"] MalwareIgnorePath = migrator.orm["malware_ignore_path"] MalwareScannedStat = migrator.orm["malware_stanned_stat"] migrator.drop_model(MalwareHit) migrator.drop_model(MalwareScan) migrator.drop_model(MalwareIgnorePath) migrator.drop_model(MalwareScannedStat) defence360agent/migrations/015_add_iplist_expiration_index.py0000644000000000000000000000055100000000000021427 0ustar def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_index(IPList, "expiration") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IPList = migrator.orm["iplist"] migrator.drop_index(IPList, "expiration") defence360agent/migrations/016_fix_autowhitelist_expiration.py0000644000000000000000000000071300000000000021700 0ustar from time import time _MAX_TIMEOUT = 4294967 def migrate(migrator, database, fake=False, **kwargs): IPListModel = migrator.orm["iplist"] # if expiration more that ipset limit, setting max available expiration IPListModel.update(expiration=_MAX_TIMEOUT).where( (IPListModel.listname == "WHITE") & (IPListModel.expiration - time() > _MAX_TIMEOUT) ).execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/017_remove_sensor_prefix.py0000644000000000000000000000054500000000000020132 0ustar """ Migrate server-id w/ prefix inside imunify360.id to server-id w/o prefix into i360.id file UPD: migration not needed yet, as far as, the majority of the servers already converted their server-id to w/0 prefix form. """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/018_license_info.py0000644000000000000000000000111000000000000016312 0ustar import peewee as pw class License(pw.Model): class Meta: db_table = "license" status = pw.BooleanField(primary_key=True) expiration = pw.IntegerField(null=False, default=0) limit = pw.IntegerField(null=True) redirect_url = pw.CharField(null=True) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" migrator.create_model(License) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" License = migrator.orm["license"] migrator.drop_model(License) defence360agent/migrations/019_purge_old_configs.py0000644000000000000000000000045500000000000017361 0ustar """ Purge old configs from config file to prevent of "Unknown field" errors. UPD: Not actual yet """ def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" defence360agent/migrations/020_malware_scan_types.py0000644000000000000000000000241000000000000017532 0ustar """Peewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) """ def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" # Here was a migration to add additional values to MalwareScan.type fiend # constraint. As we do not use this new values anymore, we dropped this # migrations because of problems caused by remove_model pass def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" # it's safe not to do any rollback actions because # this migration only changes Check() constraint pass defence360agent/migrations/021_add_testing_repo.py0000644000000000000000000000314400000000000017172 0ustar import logging import os from pathlib import Path from defence360agent.utils import os_version, OsReleaseInfo logger = logging.getLogger(__name__) TEST_REPO_PATH = Path("/etc/yum.repos.d/imunify360-testing.repo") CHECKSITE = "https://repo.imunify360.cloudlinux.com/defense360" RPM_KEY = "{}/RPM-GPG-KEY-CloudLinux".format(CHECKSITE) # disabled by default TEMPLATE_REPO = r""" [imunify360-testing] name=EL-{version} - Imunify360 baseurl={CHECKSITE}/el/{version}/updates-testing/x86_64/ username=defense360 password=nraW!F@\$x4Xd6HHQ enabled=0 gpgcheck=1 gpgkey={RPM_KEY} """ def install_repo(version): if version in (6, 7): if not TEST_REPO_PATH.exists(): TEST_REPO_PATH.write_text( TEMPLATE_REPO.format( version=version, CHECKSITE=CHECKSITE, RPM_KEY=RPM_KEY ) ) else: logger.info("Version {} is not supported".format(version)) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: if OsReleaseInfo.id_like() & OsReleaseInfo.RHEL_FEDORA_CENTOS: version = None full_version = os_version() if full_version.startswith("6"): version = 6 elif full_version.startswith("7"): version = 7 install_repo(version) except Exception as e: logger.warning("Unable to add imunify360-testing repo: %s", e) def rollback(migrator, database, fake=False, **kwargs): if fake: return try: os.remove(TEST_REPO_PATH) except Exception as e: logger.warning(str(e)) defence360agent/migrations/022_mod_security_vendors_migrations.py0000644000000000000000000000024100000000000022356 0ustar """ No need to user now """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.py0000644000000000000000000000221100000000000023556 0ustar """Using ModSecurity 'WordPress login attempt' rule instead of OSSEC one. This migration is needed in order to add new rule to config after update, because config is non replaceable. """ import os import shutil from defence360agent.contracts.config import IConfigFile, LocalConfig SECTION = "MOD_SEC_BLOCK_BY_CUSTOM_RULE" RULE_ID = "33332" # WordPress login attempt RULE_VALUES = {"max_incident_repetition": 10, "check_period": 120} def migrate(migrator, database, fake=False, **kwargs): if fake: return local_config: IConfigFile = LocalConfig() if not os.path.exists(local_config.path): return if not os.path.isfile(local_config.path): return shutil.copyfile(local_config.path, local_config.path + ".old") new_conf = local_config.config_to_dict() new_conf.setdefault(SECTION, {})[RULE_ID] = RULE_VALUES local_config.dict_to_config(new_conf, validate=False) def rollback(migrator, database, fake=False, **kwargs): if fake: return local_config: IConfigFile = LocalConfig() old = local_config.path + ".old" if os.path.isfile(old): shutil.move(old, local_config.path) defence360agent/migrations/024_ignore_from_graylist.py0000644000000000000000000000072700000000000020113 0ustar import peewee as pw class IgnoreList(pw.Model): ip = pw.CharField(primary_key=True, null=False) class Meta: db_table = "ignore_list" def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" migrator.create_model(IgnoreList) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" IgnoreList = migrator.orm["ignore_list"] migrator.drop_model(IgnoreList) defence360agent/migrations/025_malware_config_realtime.py0000644000000000000000000000126700000000000020527 0ustar import os import yaml from defence360agent.contracts.config import LocalConfig def migrate(migrator, database, fake=False, **kwargs): if fake: return local_config = LocalConfig() if not os.path.exists(local_config.path): return with open(local_config.path) as f: conf = yaml.safe_load(f) malware_settings = conf.setdefault("MALWARE_SCANNING", {}) value = malware_settings.pop("enable_scan_uploaded_files", True) malware_settings["enable_scan_pure_ftpd"] = value malware_settings["enable_scan_modsec"] = value local_config.dict_to_config(conf, validate=False) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/026_remove_old_temporary_file.py0000644000000000000000000000107100000000000021116 0ustar import glob import os import tempfile def migrate(migrator, database, fake=False, **kwargs): if fake: return tmp_dir = tempfile.gettempdir() # fix bugs of 2.1 version path = os.path.join(tmp_dir, "predict_model_description.json") if os.path.isfile(path): os.remove(path) # fix bugs of 2.2 version pattern = os.path.join(tmp_dir, "imunify360*") for filename in glob.glob(pattern): if os.path.isfile(filename): os.remove(filename) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/027_disable_comdo_fp_rules.py0000644000000000000000000000035000000000000020345 0ustar """ Current migration doesn't needed, because apache will be restarted in the other migrations """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/028_set_permanent_ttl_for_blacklist.py0000644000000000000000000000053600000000000022316 0ustar PERMANENT_TTL = 0 def migrate(migrator, database, fake=False, **kwargs): IPListModel = migrator.orm["iplist"] IPListModel.update(expiration=PERMANENT_TTL).where( (IPListModel.listname == "BLACK") & (IPListModel.expiration != PERMANENT_TTL) ).execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/029_custom_quarantine.py0000644000000000000000000000015700000000000017432 0ustar """ Quarantine is removed in DEF-15234""" def migrate(*_, **__): pass def rollback(*_, **__): pass defence360agent/migrations/030_rename_max_incident_repetition.py0000644000000000000000000000256300000000000022117 0ustar from defence360agent.contracts.config import IConfig, LocalConfig from defence360agent.utils import log_error_and_ignore @log_error_and_ignore() def migrate( migrator, database, fake=False, config_file: IConfig = LocalConfig(), **kwargs ): if fake: return config = config_file.config_to_dict() if not config: return # rename `max_incident_repetition` to `max_incidents` block_by_severity = config.setdefault("MOD_SEC_BLOCK_BY_SEVERITY", {}) value = block_by_severity.pop("max_incident_repetition", None) if value: block_by_severity["max_incidents"] = value custom_rule_list = config.setdefault("MOD_SEC_BLOCK_BY_CUSTOM_RULE", {}) for custom_rule_conf in custom_rule_list.values(): value = custom_rule_conf.pop("max_incident_repetition", None) if value: custom_rule_conf["max_incidents"] = value if config.get("INCIDENT_LIST"): # rename section `INCIDENT_LIST` to `INCIDENT_LOGGING` config["INCIDENT_LOGGING"] = config.pop("INCIDENT_LIST", {}) # move fields auto_cleanup_conf = config.pop("AUTOCLEANUP", None) if auto_cleanup_conf: config["INCIDENT_LOGGING"].update(**auto_cleanup_conf) config_file.dict_to_config(config, validate=False, overwrite=True) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/031_add_mode_field.py0000644000000000000000000000075000000000000016560 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" MalwareHits = migrator.orm["malware_hits"] migrator.add_fields(MalwareHits, mode=pw.IntegerField(null=True)) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "mode") defence360agent/migrations/031_modsec_config_for_plesk_include.py0000644000000000000000000000272000000000000022226 0ustar from logging import getLogger from defence360agent.utils import run_coro from defence360agent.utils import antivirus_mode logger = getLogger(__name__) @antivirus_mode.skip def migrate(migrator, database, fake=False, **kwargs): try: from im360.subsys.panels.plesk import Plesk from im360.subsys.panels.plesk.mod_security import ModSecSettings except ImportError: return try: if ( fake or not Plesk.is_installed() or not run_coro(Plesk.installed_modsec()) ): return ModSecSettings.include_modsec_conf() from defence360agent.subsys.web_server import graceful_restart_sync graceful_restart_sync() except Exception as e: logger.warning("Error during web-server update: %s", str(e)) @antivirus_mode.skip def rollback(migrator, database, fake=False, **kwargs): try: from im360.subsys.panels.plesk import Plesk from im360.subsys.panels.plesk.mod_security import ModSecSettings except ImportError: return try: if ( fake or not Plesk.is_installed() or not run_coro(Plesk.installed_modsec()) ): return ModSecSettings.revert_conf_include() from defence360agent.subsys.web_server import graceful_restart_sync graceful_restart_sync() except Exception as e: logger.warning("Error during web-server update: %s", str(e)) defence360agent/migrations/032_chmod_quarantine.py0000644000000000000000000000015700000000000017204 0ustar """ Quarantine is removed in DEF-15234""" def migrate(*_, **__): pass def rollback(*_, **__): pass defence360agent/migrations/033_disable_cphulk.py0000644000000000000000000000117500000000000016636 0ustar import os import subprocess from defence360agent.contracts.config import Packaging from logging import getLogger logger = getLogger(__name__) def disable_3rdparty(): try: subprocess.check_call( [ "%s/scripts/disable_3rd_party_ids" % Packaging.DATADIR, "--nocheck", ] ) except subprocess.CalledProcessError as e: logger.error(e) def migrate(migrator, database, fake=False, **kwargs): if fake or not os.path.isfile(Packaging.DATADIR): return disable_3rdparty() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/034_hits_extras.py0000644000000000000000000000141100000000000016214 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" MalwareHit = migrator.orm["malware_hits"] class MalwareHitExtra(pw.Model): class Meta: db_table = "malware_hit_extras" id = pw.IntegerField(primary_key=True) hit = pw.ForeignKeyField(MalwareHit, null=False, related_name="extras") name = pw.CharField(null=False) value = pw.CharField(null=False) migrator.create_model(MalwareHitExtra) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" MalwareHitExtra = migrator.orm["malware_hit_extras"] migrator.remove_model(MalwareHitExtra) defence360agent/migrations/035_add_dos_expiration_field.py0000644000000000000000000000055000000000000020665 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.add_fields( IPList, dos_expiration=pw.IntegerField(default=0, null=True) ) def rollback(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "dos_expiration") defence360agent/migrations/036_add_block_port.py0000644000000000000000000000270600000000000016637 0ustar import peewee as pw class BlockedPort(pw.Model): """ Port + protocol for blocking data """ port = pw.IntegerField(null=False) proto = pw.CharField( null=False, constraints=[pw.Check("proto in ('tcp', 'udp', 'all')")] ) comment = pw.CharField(null=True) class Meta: db_table = "blocked_port" indexes = ( # create an unique on port/proto (("port", "proto"), True), ) class IgnoredByPort(pw.Model): """ Ignored IPs for port + protocol """ port_proto = pw.ForeignKeyField( BlockedPort, null=False, on_delete="CASCADE", related_name="ips" ) ip = pw.CharField(null=False) comment = pw.CharField(null=True) class Meta: db_table = "ignored_by_port_proto" indexes = ( # create an unique on port/ip (("port_proto", "ip"), True), ) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(BlockedPort) migrator.create_model(IgnoredByPort) IPList = migrator.orm["iplist"] migrator.add_fields(IPList, full_access=pw.BooleanField(null=True)) def rollback(migrator, database, fake=False, **kwargs): BlockedPort = migrator.orm["blocked_port"] IgnoredByPort = migrator.orm["blocked_port_ip"] IPList = migrator.orm["iplist"] migrator.remove_model(BlockedPort) migrator.remove_model(IgnoredByPort) migrator.remove_fields(IPList, "full_access") defence360agent/migrations/037_disabled_rules.py0000644000000000000000000000217400000000000016652 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" class DisabledRule(pw.Model): class Meta: db_table = "disabled_rules" indexes = ((("plugin", "rule_id"), True),) id = pw.PrimaryKeyField() plugin = pw.CharField(null=False) rule_id = pw.CharField(null=False) name = pw.TextField(null=False) class DisabledRuleDomain(pw.Model): disabled_rule_id_id = pw.ForeignKeyField( DisabledRule, backref="domains", on_delete="CASCADE" ) domain = pw.CharField(null=False) class Meta: db_table = "disabled_rules_domains" primary_key = pw.CompositeKey("disabled_rule_id_id", "domain") migrator.create_model(DisabledRule) migrator.create_model(DisabledRuleDomain) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" migrator.remove_model(migrator.orm["disabled_rules_domains"]) migrator.remove_model(migrator.orm["disabled_rules"]) defence360agent/migrations/038_disabled_rules_import.py0000644000000000000000000000135500000000000020245 0ustar import logging from defence360agent.contracts.config import IConfig, LocalConfig logger = logging.getLogger(__name__) def migrate( migrator, database, fake=False, config_file: IConfig = LocalConfig(), **kwargs ): """Write your migrations here.""" if fake: return config = config_file.config_to_dict() if not config: return # deleting "OSSEC" section config.pop("OSSEC", {}) # deleting "MOD_SEC_BLOCK_BY_SEVERITY:ignore" field config.get("MOD_SEC_BLOCK_BY_SEVERITY", {}).pop("ignore", []) config_file.dict_to_config(config, overwrite=True, validate=False) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/039_fix_malware_hits.py0000644000000000000000000000162100000000000017214 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" migrator.sql( """ CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" VARCHAR(255) NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) """ ) migrator.sql("INSERT INTO malware_hits_new SELECT * FROM malware_hits") migrator.sql("DROP TABLE malware_hits") migrator.sql("ALTER TABLE malware_hits_new RENAME TO malware_hits") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/040_ignore_mod_sec_rule_214920.py0000644000000000000000000000025700000000000020507 0ustar """Migration was buggy, so skipping it""" def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/041_fix_invalid_ignore_filed.py0000644000000000000000000000032200000000000020657 0ustar """Adding 214920 rule to ignored on disabled rules level in 038 migration""" def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/042_rebuildinstalledssldb.py0000644000000000000000000000116500000000000020242 0ustar import logging import subprocess from defence360agent.utils import antivirus_mode logger = logging.getLogger(__name__) @antivirus_mode.skip def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from im360.subsys.panels.cpanel import cPanel except ImportError: return if cPanel.is_installed(): try: subprocess.run(["/scripts/rebuildinstalledssldb"]) except Exception as e: logger.warning("Failed to rebuild cpanel ssl db: %s", str(e)) @antivirus_mode.skip def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/043_disable_dos_scan_by_default.py0000644000000000000000000000070700000000000021340 0ustar from defence360agent.contracts.config import ConfigFile def migrate(migrator, database, fake=False, **kwargs): if fake: return config_file = ConfigFile() config = config_file.config_to_dict() if not config: return dos_settings = config.setdefault("DOS", {}) dos_settings["enabled"] = False config_file.dict_to_config(config, validate=False) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/044_ignore_virtfs_on_cpanel.py0000644000000000000000000000070000000000000020556 0ustar """ This migration adds cpanel virtfs directory (/home/virtfs) to ignore for malware scanning """ from defence360agent.subsys.panels.cpanel import cPanel def migrate(migrator, database, fake=False, **kwargs): if (not fake) and cPanel.is_installed(): MalwareIgnorePath = migrator.orm["malware_ignore_path"] MalwareIgnorePath.get_or_create(path="/home/virtfs") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/045_ignore_vdserver_dir_in_csf.py0000644000000000000000000000052300000000000021246 0ustar import os from defence360agent.utils import append_with_newline CSF_FIGNORE = "/etc/csf/csf.fignore" def migrate(migrator, database, fake=False, **kwargs): if (not fake) and os.path.isfile(CSF_FIGNORE): append_with_newline(CSF_FIGNORE, "/tmp/.vdserver\n") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/046_foreign_key_fix.py0000644000000000000000000000155300000000000017040 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" migrator.sql( """ CREATE TABLE "malware_hit_extras_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "hit_id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "value" VARCHAR(255) NOT NULL, FOREIGN KEY ("hit_id") REFERENCES "malware_hits" ("id") ON DELETE CASCADE ) """ ) migrator.sql( "INSERT INTO malware_hit_extras_new SELECT * FROM malware_hit_extras" ) migrator.sql("DROP TABLE malware_hit_extras") migrator.sql( "ALTER TABLE malware_hit_extras_new RENAME TO malware_hit_extras" ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/047_license_in_file.py0000644000000000000000000000110000000000000016765 0ustar import json from playhouse.shortcuts import model_to_dict FALLBACK_LICENSE_FILE = "/var/imunify360/license_old.json" def migrate(migrator, database, fake=False, **kwargs): if fake: return LicenseModel = migrator.orm["license"] lic, _ = LicenseModel.get_or_create( defaults={ "status": True, "expiration": 0, } ) with open(FALLBACK_LICENSE_FILE, "w") as f: json.dump(model_to_dict(lic), f) migrator.remove_model(LicenseModel) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/048_malware_hits_vendor_field.py0000644000000000000000000000066400000000000021074 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.add_fields( MalwareHits, vendor=pw.CharField(null=False, default="clamav") ) def rollback(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "vendor") defence360agent/migrations/049_add_auto_added_field_to_iplist.py0000644000000000000000000000103300000000000022017 0ustar """ Introducing new filed `auto_whitelisted` in order to mark IPs that were autowhitelied during `--remote-addr` flag. This will help to differentiate such IPs in UI. """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.add_fields( IPList, auto_whitelisted=pw.BooleanField(default=False, null=True) ) def rollback(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "auto_whitelisted") defence360agent/migrations/050_fill_auto_whitelisted.py0000644000000000000000000000070200000000000020242 0ustar """ Filling `auto_whitelisted` filed that was added in previous 049 migration. Matching IPs that were auto added earlier. """ def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] # mark previously autowhitelisted IPList.update(auto_whitelisted=True).where( IPList.comment.startswith("IP auto-whitelisted with") ).execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/051_cleanup_vd_license.py0000644000000000000000000000163200000000000017505 0ustar import json import logging from contextlib import suppress logger = logging.getLogger(__name__) class VirusdieLicense: CONFIG_FILE = "/usr/local/vdserver/config.json" def unregister(self): self._write_key("") def _write_key(self, key): with open(self.CONFIG_FILE) as read_file: content = json.load(read_file) content["vdbApiKey"] = key with open(self.CONFIG_FILE, "w") as write_file: json.dump( content, write_file, sort_keys=True, indent=2, separators=(",", ": "), ) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" with suppress(FileNotFoundError): VirusdieLicense().unregister() def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/052_whitelisted_crawlers.py0000644000000000000000000000205400000000000020112 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" class WhitelistedCrawler(pw.Model): class Meta: db_table = "whitelisted_crawlers" id = pw.PrimaryKeyField() description = pw.TextField(null=False) class WhitelistedCrawlerDomain(pw.Model): class Meta: db_table = "whitelisted_crawler_domains" id = pw.PrimaryKeyField() crawler = pw.ForeignKeyField( WhitelistedCrawler, null=False, on_delete="CASCADE", related_name="domains", ) domain = pw.TextField(null=False) migrator.create_model(WhitelistedCrawler) migrator.create_model(WhitelistedCrawlerDomain) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" migrator.remove_model(migrator.orm["whitelisted_crawlers"]) migrator.remove_model(migrator.orm["whitelisted_crawler_domains"]) defence360agent/migrations/053_populate_whitelisted_crawlers.py0000644000000000000000000000251300000000000022024 0ustar import logging logger = logging.getLogger(__name__) DATA = [ ( "Google (https://support.google.com/webmasters/answer/80553?hl=ru)", # noqa [".google.com", ".googlebot.com"], ), ( ( # noqa "Yandex" " (https://yandex.ru/support/webmaster/robot-workings/check-yandex-robots.xml?lang=ru)" # NOQA E501 ), [".yandex.ru", ".yandex.com", ".yandex.net"], ), ( ( # noqa "Bing (https://www.bing.com/webmaster/help/how-to-verify-bingbot-3905dc26)" # NOQA E501 ), [".search.msn.com"], ), ( ( # noqa "Baidu" " (http://help.baidu.com/question?prod_en=master&class=Baiduspider)" ), [".baidu.com", ".baidu.jp"], ), ] def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" if fake: return wc = migrator.orm["whitelisted_crawlers"] wcd = migrator.orm["whitelisted_crawler_domains"] with database.atomic(): for descr, domains in DATA: inserted_id = wc.insert(description=descr).execute() for d in domains: wcd.insert(crawler=inserted_id, domain=d).execute() def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/054_add_malicious_and_added_date_fileds.py0000644000000000000000000000135200000000000022750 0ustar from time import time from peewee import BooleanField, IntegerField def migrate(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.add_fields( MalwareHits, malicious=BooleanField(null=False, default=False) ) MalwareIgnorePath = migrator.orm["malware_ignore_path"] migrator.add_fields( MalwareIgnorePath, added_date=IntegerField(null=False, default=lambda: int(time())), ) def rollback(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "malicious") MalwareIgnorePath = migrator.orm["malware_ignore_path"] migrator.remove_fields(MalwareIgnorePath, "added_date") defence360agent/migrations/055_migrate_move_to_quar_option.py0000644000000000000000000000106200000000000021464 0ustar from defence360agent.contracts.config import ConfigFile, IConfig def migrate(*_, fake=False, config_file: IConfig = ConfigFile(), **__): if fake: return if not (config := config_file.config_to_dict()): return malware_settings = config.get("MALWARE_SCANNING", {}) malware_settings.pop("leave_suspicious", None) malware_settings.pop("max_days_in_quarantine", None) malware_settings.pop("move_to_quarantine", False) config_file.dict_to_config(config, overwrite=True, validate=False) def rollback(*_, **__): pass defence360agent/migrations/056_populate_malicious_with_quarantined.py0000644000000000000000000000015700000000000023215 0ustar """ Quarantine is removed in DEF-15234""" def migrate(*_, **__): pass def rollback(*_, **__): pass defence360agent/migrations/057_filename_is_blob.py0000644000000000000000000000204300000000000017137 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """ This migration os only for consistency, actually all works with CharField as well """ migrator.sql( """ CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" BLOB NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, "vendor" VARCHAR(255) NOT NULL, "malicious" INTEGER NOT NULL, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) """ ) migrator.sql("INSERT INTO malware_hits_new SELECT * FROM malware_hits") migrator.sql("DROP TABLE malware_hits") migrator.sql("ALTER TABLE malware_hits_new RENAME TO malware_hits") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/058_convert_license_last_attempt.py0000644000000000000000000000020500000000000021630 0ustar # Removed, because we do not have customers with old-style license def migrate(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/059_scans_error_field.py0000644000000000000000000000057400000000000017362 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): MalwareScans = migrator.orm["malware_scans"] migrator.add_fields( MalwareScans, error=pw.TextField(default=None, null=True) ) def rollback(migrator, database, fake=False, **kwargs): MalwareScans = migrator.orm["malware_scans"] migrator.remove_fields(MalwareScans, "error") defence360agent/migrations/061_migrate_backup_system_conf.py0000644000000000000000000000250500000000000021252 0ustar """ Migrate backup config from user oriented config file to the separate internal file """ import os from typing import Optional from defence360agent.contracts.config import ( BackupConfig, IConfig, IConfigFile, LocalConfig, ) from defence360agent.utils import antivirus_mode @antivirus_mode.skip def migrate( migrator, database, fake=False, config_file: IConfig = LocalConfig(), backup_config_file: Optional[IConfigFile] = None, **kwargs, ): if fake: return if backup_config_file is None: backup_config_file = BackupConfig() if not (config_from := config_file.config_to_dict()): return # Do not overwrite existing config file if os.path.exists(backup_config_file.path): return backup_conf_current = config_from.get("BACKUP_RESTORE", {}) config_to = { "BACKUP_SYSTEM": { "enabled": backup_conf_current.pop("enabled", False), "backup_system": backup_conf_current.pop("backup_system", None), } } backup_config_file.dict_to_config( config_to, overwrite=True, validate=False ) config_file.dict_to_config(config_from, overwrite=True, validate=False) @antivirus_mode.skip def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/062_drop_malware_extra_data.py0000644000000000000000000000046500000000000020540 0ustar def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" MalwareExtraData = migrator.orm["malware_hit_extras"] migrator.remove_model(MalwareExtraData) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/062_fix_null_expiration.py0000644000000000000000000000061200000000000017744 0ustar """ Fix IPs that were added with NULL expiration to the WB lists """ def migrate(migrator, database, fake=False, **kwargs): IPListModel = migrator.orm["iplist"] IPListModel.update(expiration=0).where( (IPListModel.listname.in_(["WHITE", "BLACK"])) & (IPListModel.expiration.is_null()) ).execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.py0000644000000000000000000000054100000000000024757 0ustar def migrate(migrator, database, fake=False, **kwargs): IPListModel = migrator.orm["iplist"] IPListModel.update(expiration=IPListModel.dos_expiration).where( (IPListModel.listname == "GRAY") & (IPListModel.expiration < IPListModel.dos_expiration) ).execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/064_chmod_i360deploy_log.py0000644000000000000000000000044000000000000017574 0ustar from contextlib import suppress import os I360DEPLOY_LOG = "/var/log/i360deploy.log" def migrate(migrator, database, fake=False, **kwargs): with suppress(FileNotFoundError): os.chmod(I360DEPLOY_LOG, 0o600) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/065_remove_capture_csf_lock_from_config.py0000644000000000000000000000114100000000000023116 0ustar import logging import os from defence360agent.contracts.config import LocalConfig logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return local_config = LocalConfig() if not os.path.exists(local_config.path): return config = local_config.config_to_dict() if "CSF_COOPERATION" in config: config.pop("CSF_COOPERATION") local_config.dict_to_config(config, overwrite=True, validate=False) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/066_eula_table.py0000644000000000000000000000061700000000000015770 0ustar import peewee as pw class Eula(pw.Model): class Meta: db_table = "eula" updated = pw.DateField(primary_key=True) accepted = pw.IntegerField(null=True, default=None) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(Eula) def rollback(migrator, database, fake=False, **kwargs): Eula = migrator.orm["eula"] migrator.remove_model(Eula) defence360agent/migrations/067_drop_fields_from_modsec_conf.py0000644000000000000000000000107400000000000021546 0ustar from defence360agent.contracts.config import IConfig, LocalConfig def migrate( migrator, database, fake=False, config_file: IConfig = LocalConfig(), **kwargs ): if fake: return conf = config_file.config_to_dict() if not conf: return mod_sec_settings = conf.setdefault("MOD_SEC", {}) mod_sec_settings.pop("was_installed", None) mod_sec_settings.pop("OWASP_deleted", None) config_file.dict_to_config(conf, validate=False, overwrite=True) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/068_remove_rules_check_interval_from_config.py0000644000000000000000000000107700000000000024016 0ustar import logging from defence360agent.contracts.config import ConfigFile logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return config_file = ConfigFile() config = config_file.config_to_dict() if not config: return if "IPTABLES_RULE_CHECK" in config: config.pop("IPTABLES_RULE_CHECK") config_file.dict_to_config(config, overwrite=True, validate=False) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/069_incidents_domain_field.py0000644000000000000000000000052600000000000020347 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): Incident = migrator.orm["incident"] migrator.add_fields(Incident, domain=pw.TextField(default=None, null=True)) def rollback(migrator, database, fake=False, **kwargs): Incident = migrator.orm["incident"] migrator.remove_fields(Incident, "domain") defence360agent/migrations/070_modsec_incident_names.py0000644000000000000000000000250400000000000020175 0ustar from tempfile import TemporaryFile def extract_name(description): return description.split("||", maxsplit=1)[0] def migrate(migrator, database, fake=False, **kwargs): """ This migration extracts incident name from whole mod_security message Centos6 version of sqlite does not have instr(), using slow python-based way """ incident = migrator.orm["incident"] # FIXME: after migrating to peewee 3 remove the try..except block def select_incidents(): try: yield from ( incident.select(incident.id, incident.description) .where(incident.plugin == "modsec") .where(incident.description.contains("||")) .tuples() .iterator() ) except RuntimeError: return with TemporaryFile(mode="w+") as f: for id_, desc in select_incidents(): f.write("{},{}\n".format(id_, extract_name(desc))) f.seek(0) with database.atomic(): for line in f: id_, name = line.split(",", maxsplit=1) incident.update(name=name.strip()).where( incident.id == int(id_) ).execute() def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/071_malware_hits_hash_size_fields.py0000644000000000000000000000102700000000000021725 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" MalwareHits = migrator.orm["malware_hits"] migrator.add_fields( MalwareHits, size=pw.CharField(null=True), hash=pw.CharField(null=True) ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "hash", "size") defence360agent/migrations/072_add_malware_history_table.py0000644000000000000000000000130400000000000021052 0ustar from time import time import peewee as pw from defence360agent.model.simplification import FilenameField class MalwareHistory(pw.Model): class Meta: db_table = "malware_history" path = FilenameField(null=False) event = pw.CharField(null=False) initiator = pw.CharField(null=False) cause = pw.CharField(null=False) file_owner = pw.CharField(null=True) ctime = pw.IntegerField(null=True, default=lambda: int(time())) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(MalwareHistory) def rollback(migrator, database, fake=False, **kwargs): MalwareHistory = migrator.orm["malware_history"] migrator.remove_model(MalwareHistory) defence360agent/migrations/072_captcha_stat.py0000644000000000000000000000167300000000000016331 0ustar import peewee as pw class Country(pw.Model): """ Contains country code and name """ id = pw.CharField(primary_key=True, null=False) code = pw.CharField(max_length=2, unique=True, null=False) name = pw.CharField(null=False) class Meta: db_table = "country" class CaptchaStat(pw.Model): class Meta: db_table = "captcha_stat" primary_key = pw.CompositeKey( "event", "ip", "country", "domain", "timestamp" ) event = pw.TextField(null=False) ip = pw.TextField(null=False) country = pw.ForeignKeyField(Country, null=True) domain = pw.TextField(null=True) timestamp = pw.IntegerField(null=False) count = pw.IntegerField(null=False) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(CaptchaStat) def rollback(migrator, database, fake=False, **kwargs): cs = migrator.orm["captcha_stat"] migrator.remove_model(cs) defence360agent/migrations/072_extend_last_synclist.py0000644000000000000000000000120000000000000020117 0ustar def migrate(migrator, database, fake=False, **kwargs): """Recreating DB in order to make `name` as primary key""" migrator.sql( """ CREATE TABLE "last_synclist_new" ( "timestamp" REAL, "name" VARCHAR(255) NOT NULL PRIMARY KEY )""" ) migrator.sql( "INSERT INTO last_synclist_new " 'SELECT timestamp, "ip" AS name FROM last_synclist LIMIT 1' ) migrator.sql("DROP TABLE last_synclist") migrator.sql("ALTER TABLE last_synclist_new RENAME TO last_synclist") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" defence360agent/migrations/073_drop_dos_expiration.py0000644000000000000000000000112700000000000017741 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPList = migrator.orm["iplist"] migrator.add_fields( IPList, no_captcha=pw.BooleanField(null=False, default=False), ) migrator.sql( "UPDATE iplist SET no_captcha=1 " "WHERE listname='GRAY' AND dos_expiration" ) migrator.remove_fields(IPList, "dos_expiration") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/074_ip_as_int.py0000644000000000000000000000301100000000000015626 0ustar import logging from time import time import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" Country = migrator.orm["country"] class IPListNew(pw.Model): # available list names ip = pw.CharField(null=False) listname = pw.CharField( null=False, constraints=[pw.Check("listname in ('WHITE','BLACK','GRAY')")], ) expiration = pw.IntegerField( default=0, null=True # 0 - never ) # null - the same :( imported_from = pw.CharField(null=True) ctime = pw.IntegerField( null=True, default=lambda: int(time()) # those ) # are OK deep = pw.IntegerField(null=True) comment = pw.CharField(null=True) country = pw.ForeignKeyField(Country, null=True) no_captcha = pw.BooleanField(null=False, default=False) full_access = pw.BooleanField(null=True) auto_whitelisted = pw.BooleanField(null=True, default=False) network_address = pw.IntegerField(null=False) netmask = pw.IntegerField(null=False) version = pw.IntegerField(null=False) class Meta: db_table = "iplist_new" primary_key = pw.CompositeKey( "network_address", "netmask", "version" ) migrator.create_model(IPListNew) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/075_ips_as_int.py0000644000000000000000000000333000000000000016016 0ustar import logging import peewee as pw import ipaddress logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" IPListNew = migrator.orm["iplist_new"] IPList = migrator.orm["iplist"] def iplist_select(): # FIXME: remove this function after migrating to peewee 3 try: yield from IPList.select(IPList).dicts().iterator() except RuntimeError: return try: from im360.utils.net import pack_ip_network except ImportError: pass else: with database.atomic(): for ip_obj in iplist_select(): try: ip = ipaddress.ip_network(ip_obj["ip"]) except ValueError: # malformed ip continue net, mask, version = pack_ip_network(ip) ip_obj.update( { "network_address": net, "netmask": mask, "version": version, } ) try: IPListNew.insert(ip_obj).execute() except pw.IntegrityError as e: logger.warning("Error inserting IP: %s", e) migrator.sql("DROP TABLE iplist") migrator.sql("ALTER TABLE iplist_new RENAME TO iplist") migrator.sql('CREATE INDEX "iplist_listname" ON "iplist" ("listname")') migrator.sql('CREATE INDEX "iplist_expiration" ON "iplist" ("expiration")') migrator.sql('CREATE INDEX "iplist_ip" ON "iplist" ("ip")') def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/076_hash_model.py0000644000000000000000000000020300000000000015766 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/077_alter_malware_scan.py0000644000000000000000000000145600000000000017522 0ustar """ Altering MalwareScan.type in order to add ability to support 'malware-response' scan type """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] MalwareScan.update(type="realtime").where( MalwareScan.type == "inotify" ).execute() migrator.change_fields( MalwareScan, path=pw.CharField(null=True, default="") ) migrator.change_fields( MalwareScan, type=pw.CharField( null=False, constraints=[ pw.Check( "type in ('on-demand', 'realtime', 'malware-response')" ) ], ), ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/078_fix_signatures_permissions.py0000644000000000000000000000041500000000000021357 0ustar """Removed, as DEF-11611 will fix folder creations so it will not be needed anymore. """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/079_add_uid_gid_fields.py0000644000000000000000000000072200000000000017436 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.add_fields( MalwareHit, uid=pw.IntegerField(null=True), gid=pw.IntegerField(null=True), ) def rollback(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHit, "uid", "gid") defence360agent/migrations/080_populate_uid_gid_size_hash_fields.py0000644000000000000000000000441300000000000022565 0ustar import errno import hashlib import logging import os import stat logger = logging.getLogger(__name__) def _hash_and_size_from_fd(fd, hash_func, chunksize=4096): """Read an open file descriptor in chunks; return (hexdigest, size).""" hash_ = hash_func() size = 0 while True: chunk = os.read(fd, chunksize) if not chunk: break hash_.update(chunk) size += len(chunk) return hash_.hexdigest(), size def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] try: for hit in MalwareHit.select(): path = hit.orig_file.encode("utf-8", errors="surrogateescape") # Open with O_NOFOLLOW so a symlinked malware path cannot redirect # the subsequent fchown/read to an attacker-chosen target file. # O_NONBLOCK guards against accidentally hanging on a FIFO that # an attacker may have substituted for the recorded file. try: fd = os.open( path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, ) except FileNotFoundError: logger.warning( "Malware file %s does not exist, skipping", path ) continue except OSError as e: if e.errno == errno.ELOOP: logger.warning( "Malware file %s is a symlink, skipping", path ) continue raise try: st = os.fstat(fd) if not stat.S_ISREG(st.st_mode): logger.warning( "Malware file %s is not a regular file, skipping", path, ) continue if hit.mode is not None and not hit.restored: os.fchown(fd, 0, 0) hit.uid, hit.gid = st.st_uid, st.st_gid hit.hash, hit.size = _hash_and_size_from_fd(fd, hashlib.sha256) finally: os.close(fd) hit.save() except Exception as e: logger.exception(e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/081_fix_clamscan_broken_symlink.py0000644000000000000000000000020300000000000021414 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/082_add_cl_on_premise_backup_option.py0000644000000000000000000000102400000000000022225 0ustar # This migration was used to add `cl_on_premise_backup_allowed` option # to config file with the default value. The only purpose of this # migration was to display new option in the config file. Now we have a # separate file for this purpose stored at # /etc/sysconfig/imunify360/imunify360.config.defaults.example # It is created dynamically with # src/asyncclient/scripts/create_default_config.py def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/082_add_manual_flag.py0000644000000000000000000000053400000000000016745 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.add_fields( IPList, manual=pw.BooleanField(null=False, default=True) ) def rollback(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "manual") defence360agent/migrations/083_drop_no_captcha_field.py0000644000000000000000000000154100000000000020155 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.sql("UPDATE iplist SET manual=0 WHERE listname='GRAY'") migrator.sql("UPDATE iplist SET manual=1 WHERE listname='WHITE'") migrator.sql("UPDATE iplist SET manual=1 WHERE listname='BLACK'") migrator.sql( "UPDATE iplist SET listname='BLACK'" "WHERE listname='GRAY' AND no_captcha=1" ) migrator.sql( "UPDATE iplist SET " "comment='Automatically blocked due to distributed attack', " "imported_from='Imunify360'" " WHERE listname='BLACK' AND manual=0" ) migrator.remove_fields(IPList, "no_captcha") def rollback(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.add_fields(IPList, no_captcha=pw.BooleanField(default=False)) defence360agent/migrations/084_country_subnets_fields.py0000644000000000000000000000121200000000000020457 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): CountrySubnets = migrator.orm["country_subnets"] migrator.rename_field(CountrySubnets, "ip_net", "ip") migrator.add_fields( CountrySubnets, network_address=pw.IntegerField(null=True), netmask=pw.IntegerField(null=True), version=pw.IntegerField(null=True), ) def rollback(migrator, database, fake=False, **kwargs): CountrySubnets = migrator.orm["country_subnets"] migrator.rename_field(CountrySubnets, "ip", "ip_net") migrator.remove_fields( CountrySubnets, "network_address", "netmask", "version" ) defence360agent/migrations/085_country_subnets_fields.py0000644000000000000000000000117700000000000020472 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return CountrySubnets = migrator.orm["country_subnets"] migrator.sql("DELETE FROM country_subnets") migrator.add_not_null(CountrySubnets, "network_address") migrator.add_not_null( CountrySubnets, "netmask", ) migrator.add_not_null(CountrySubnets, "version") def rollback(migrator, database, fake=False, **kwargs): CountrySubnets = migrator.orm["country_subnets"] migrator.drop_not_null( CountrySubnets, "network_address", "netmask", "version" ) defence360agent/migrations/086_ignored_by_port_fields.py0000644000000000000000000000120700000000000020404 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IgnoredByPort = migrator.orm["ignored_by_port_proto"] Country = migrator.orm["country"] migrator.add_fields( IgnoredByPort, network_address=pw.IntegerField(null=True), netmask=pw.IntegerField(null=True), version=pw.IntegerField(null=True), country=pw.ForeignKeyField(Country, null=True), ) def rollback(migrator, database, fake=False, **kwargs): IgnoredByPort = migrator.orm["ignored_by_port_proto"] migrator.remove_fields( IgnoredByPort, "network_address", "netmask", "version", "country" ) defence360agent/migrations/087_ignored_by_port_fields.py0000644000000000000000000000334400000000000020411 0ustar import logging from ipaddress import ip_network logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): IgnoredByPort = migrator.orm["ignored_by_port_proto"] try: from im360.utils.net import pack_ip_network except ImportError: ips = [] # keep database structure in AV too else: q = IgnoredByPort.select(IgnoredByPort.ip).distinct().tuples() ips = [ip for ip, in q] for ip in ips: try: net, mask, version = pack_ip_network(ip_network(ip)) except ValueError: logger.warning("Invalid IP network %s", ip) IgnoredByPort.delete().where(IgnoredByPort.ip == ip).execute() else: IgnoredByPort.update( network_address=net, netmask=mask, version=version ).where(IgnoredByPort.ip == ip).execute() if ips: from defence360agent.internals import geo try: with geo.reader() as geo_reader: for ip in ips: country = geo_reader.get_id(ip) IgnoredByPort.update( country=country, ).where(IgnoredByPort.ip == ip).execute() except OSError: logger.warning( "Failed to update countries data in ignored_by_port" ) migrator.add_not_null(IgnoredByPort, "network_address") migrator.add_not_null( IgnoredByPort, "netmask", ) migrator.add_not_null(IgnoredByPort, "version") def rollback(migrator, database, fake=False, **kwargs): IgnoredByPort = migrator.orm["ignored_by_port_proto"] migrator.drop_not_null( IgnoredByPort, "network_address", "netmask", "version" ) defence360agent/migrations/088_add_malware_i360_clamd_scan_option.py0000644000000000000000000000020300000000000022423 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/089_proactive_tables.py0000644000000000000000000000275500000000000017233 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): Country = migrator.orm["country"] class Proactive(pw.Model): class Meta: db_table = "proactive" id = pw.PrimaryKeyField() timestamp = pw.IntegerField(null=False) ip = pw.TextField(null=True) ip_int = pw.IntegerField(null=True) ip_version = pw.IntegerField(null=True) ip_country = pw.ForeignKeyField(Country, null=True) reason = pw.TextField(null=False) description = pw.TextField(null=True) action = pw.TextField(null=False) host = pw.TextField(null=True) path = pw.TextField(null=False) url = pw.TextField(null=True) count = pw.IntegerField(null=False) uid = pw.IntegerField(null=False) gid = pw.IntegerField(null=False) class ProactiveEnv(pw.Model): event = pw.ForeignKeyField( Proactive, null=False, on_delete="CASCADE", related_name="env" ) name = pw.TextField(null=False) value = pw.TextField(null=True) class Meta: db_table = "proactive_env" primary_key = pw.CompositeKey("event", "name", "value") migrator.create_model(Proactive) migrator.create_model(ProactiveEnv) def rollback(migrator, database, fake=False, **kwargs): ProactiveEnv = migrator.orm["proactive_env"] Proactive = migrator.orm["proactive"] migrator.remove_model(ProactiveEnv) migrator.remove_model(Proactive) defence360agent/migrations/090_safe_user_config.py0000644000000000000000000000217100000000000017166 0ustar import pwd import shutil import os import logging from defence360agent.contracts.config import Core logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): try: for user in pwd.getpwall(): try: src = os.path.join(user.pw_dir, Core.USER_CONFIG_FILE_NAME) if os.path.isfile(src) and not os.path.islink(src): dst_dir = os.path.join(Core.USER_CONFDIR, user.pw_name) os.mkdir(dst_dir) os.chown(dst_dir, 0, user.pw_gid) os.chmod(dst_dir, 0o750) dst_file = os.path.join( dst_dir, Core.USER_CONFIG_FILE_NAME ) shutil.move(src, dst_file) os.chown(dst_file, 0, user.pw_gid) os.chmod(dst_file, 0o640) except OSError as e: logger.warning("Something went wrong: %s", str(e)) except Exception: logger.exception("Failed to migrate config for %s", user) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/091_compress_old_logs.py0000644000000000000000000000173000000000000017403 0ustar import logging import shutil import gzip import os from defence360agent.contracts.config import Logger from defence360agent.internals.logger import get_log_file_names logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): for filename in get_log_file_names(): for i in range(1, Logger.BACKUP_COUNT + 1): source = f"{filename}.{i}" dest = f"{source}.gz" try: if os.path.exists(source): with open(source, "rb") as f_in, gzip.open( dest, "wb" ) as f_out: shutil.copyfileobj(f_in, f_out) os.remove(source) except Exception as e: logger.exception( "Failed file %s compression with %s", source, e ) def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/092_ignore_proc_sys_dirs.py0000644000000000000000000000060700000000000020116 0ustar """ This migration adds /proc and /sys to ignore for malware scanning """ def migrate(migrator, database, fake=False, **kwargs): if not fake: for ignored_dir in ["/proc", "/sys"]: MalwareIgnorePath = migrator.orm["malware_ignore_path"] MalwareIgnorePath.get_or_create(path=ignored_dir) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/092_remove_old_disabled_rules.py0000644000000000000000000000051700000000000021065 0ustar """ Moves disabled rules from the cPanel-specific user data directory to the centralized Apache configuration directory. No longer required running due to age and causing issues with the Coraza WAF """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/093_make_quarantined_files_immutable.py0000644000000000000000000000020300000000000022413 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/094_ignore_cagefs_proc.py0000644000000000000000000000057100000000000017511 0ustar """ This migration adds /usr/share/cagefs-skeleton/proc/ to ignore for malware scanning """ def migrate(migrator, database, fake=False, **kwargs): if not fake: MalwareIgnorePath = migrator.orm["malware_ignore_path"] MalwareIgnorePath.get_or_create(path="/usr/share/cagefs-skeleton/proc") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/095_add_total_malicious_field.py0000644000000000000000000000062600000000000021040 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.add_fields( MalwareScan, total_malicious=pw.IntegerField(null=False, default=0), ) def rollback(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.remove_fields(MalwareScan, "total_malicious") defence360agent/migrations/096_populate_total_malicious_field.py0000644000000000000000000000071100000000000022135 0ustar def migrate(migrator, database, fake=False, **kwargs): if fake: return MalwareScan = migrator.orm["malware_scans"] MalwareHit = migrator.orm["malware_hits"] for scan in MalwareScan: total_malicious = ( scan.malwarehit_set.select().where(MalwareHit.malicious).count() ) scan.total_malicious = total_malicious scan.save() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/097_remove_uid_and_gid.py0000644000000000000000000000053000000000000017474 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] try: migrator.remove_fields(MalwareHit, "uid", "gid") except Exception as e: logger.exception(e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/098_remote_proxy_tables.py0000644000000000000000000000223400000000000017763 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): class RemoteProxyGroup(pw.Model): """Groups multiple remote proxies together with common data.""" MANUAL = "manual" IMUNIFY360 = "imunify360" name = pw.CharField(null=False) source = pw.CharField( null=False, constraints=[ pw.Check("source in ('{}', '{}')".format(MANUAL, IMUNIFY360)) ], ) enabled = pw.BooleanField(null=False, default=True) class Meta: db_table = "remote_proxy_group" indexes = ((("name", "source"), True),) class RemoteProxy(pw.Model): group = pw.ForeignKeyField(RemoteProxyGroup, null=False) network = pw.TextField(null=False) class Meta: db_table = "remote_proxy" migrator.create_model(RemoteProxyGroup) migrator.create_model(RemoteProxy) def rollback(migrator, database, fake=False, **kwargs): RemoteProxy = migrator.orm["remote_proxy"] RemoteProxyGroup = migrator.orm["remote_proxy_group"] migrator.remove_model(RemoteProxy) migrator.remove_model(RemoteProxyGroup) defence360agent/migrations/099_remove_old_disabled_rules.py0000644000000000000000000000214000000000000021066 0ustar import logging import os import shutil import subprocess from defence360agent.utils import antivirus_mode, run_coro logger = logging.getLogger(__name__) OLD_DISABLED_RULES_CONFIG = "/etc/apache2/conf.d/i360_modsec_disable.conf" @antivirus_mode.skip def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from im360.subsys.panels.cpanel import cPanel except ImportError: return try: if not cPanel.is_installed() or not run_coro( cPanel.installed_modsec() ): return hp = cPanel() if os.path.exists(OLD_DISABLED_RULES_CONFIG): shutil.move( OLD_DISABLED_RULES_CONFIG, os.path.join( hp.DISABLED_RULES_CONFIG_DIR, hp.GLOBAL_DISABLED_RULES_CONFIG_FILENAME, ), ) subprocess.check_call(hp.REBUILD_HTTPDCONF_CMD) except Exception as e: logger.exception("Failed to delete old rules config with %s", e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/100_remove_captcha_ports_from_csf.py0000644000000000000000000000107300000000000021742 0ustar import os import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from im360.subsys import csf from im360.utils.net import IN, TCP except ImportError: return if not os.path.isfile(csf.CSF_CONFIG): return try: csf.remove_ports(TCP, IN, 52223, 52224, 52225, 52226) except Exception: logger.exception("Failed to remove captcha ports from csf config") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.py0000644000000000000000000000146100000000000023646 0ustar import os import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from im360.subsys import csf from im360.utils.net import IN, OUT, TCP except ImportError: return if not os.path.isfile(csf.CSF_CONFIG): return try: csf.remove_ports( TCP, IN, 44445, 55556, 6109, 25001, 445, 5060, ranges={(7770, 7800)}, ) csf.remove_ports(TCP, OUT, 6109, 25001, 445, 5060) except Exception: logger.exception( "Failed to remove unused Arconis ports from csf config" ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/102_proactive_ignore_list.py0000644000000000000000000000311400000000000020247 0ustar from time import time import peewee as pw def migrate(migrator, database, fake=False, **kwargs): class ProactiveIgnoredPath(pw.Model): """ Ignore list for proactive defence """ path = pw.TextField(null=False, primary_key=True) timestamp = pw.IntegerField(null=False, default=time) class Meta: db_table = "proactive_ignored_path" class ProactiveIgnoredRule(pw.Model): """ Specific rules ignored """ path = pw.ForeignKeyField( ProactiveIgnoredPath, null=False, on_delete="CASCADE", related_name="rules", ) rule_id = pw.IntegerField(null=False) rule_name = pw.TextField(null=False) class Meta: db_table = "proactive_ignored_rule" indexes = ((("path", "rule_id"), True),) migrator.create_model(ProactiveIgnoredPath) migrator.create_model(ProactiveIgnoredRule) Proactive = migrator.orm["proactive"] migrator.add_fields( Proactive, rule_id=pw.IntegerField(null=True), ) migrator.rename_field(Proactive, "reason", "rule_name") def rollback(migrator, database, fake=False, **kwargs): ProactiveIgnoredPath = migrator.orm["proactive_ignored_path"] ProactiveIgnoredRule = migrator.orm["proactive_ignored_rule"] migrator.remove_model(ProactiveIgnoredRule) migrator.remove_model(ProactiveIgnoredPath) Proactive = migrator.orm["proactive"] migrator.remove_fields(Proactive, "rule_id") migrator.rename_field(Proactive, "rule_name", "reason") defence360agent/migrations/102_replace_comodo.py0000644000000000000000000000064600000000000016637 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.sql( "UPDATE incident " "SET name=replace(name, 'COMODO WAF', 'IM360 WAF'), " "description=replace(description, 'COMODO WAF', 'IM360 WAF')" ) migrator.sql( "UPDATE disabled_rules " "SET name=replace(name, 'COMODO WAF', 'IM360 WAF')" ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/103_remove_vd_license.py0000644000000000000000000000036000000000000017346 0ustar """ Remove Virusdie registration from CLN """ from logging import getLogger logger = getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/104_add_feature_management_permissions.py0000644000000000000000000000101600000000000022750 0ustar from peewee import BooleanField, CharField, Model class FeatureManagementPerms(Model): class Meta: db_table = "feature_management_permissions" user = CharField(unique=True) proactive = BooleanField(default=True) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(FeatureManagementPerms) def rollback(migrator, database, fake=False, **kwargs): FeatureManagementPerms = migrator.orm["feature_management_permissions"] migrator.remove_model(FeatureManagementPerms) defence360agent/migrations/105_populate_default_feature_management_permissions.py0000644000000000000000000000045200000000000025561 0ustar DEFAULT = "" def migrate(migrator, database, fake=False, **kwargs): if fake: return FeatureManagementPerms = migrator.orm["feature_management_permissions"] FeatureManagementPerms.get_or_create(user=DEFAULT) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/106_add_malware_cleanup_in_config.py0000644000000000000000000000105700000000000021647 0ustar from defence360agent.contracts.config import ConfigFile def migrate(migrator, database, fake=False, **kwargs): if fake: return config_file = ConfigFile() conf = config_file.config_to_dict() if not conf: return malware_cleanup = conf.setdefault("MALWARE_CLEANUP", {}) malware_cleanup.setdefault("trim_file_instead_of_removal", True) malware_cleanup.setdefault("keep_original_files_days", 14) config_file.dict_to_config(conf, validate=False) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/106_malware_hit_status_field_add.py0000644000000000000000000000124100000000000021532 0ustar import logging from peewee import CharField, FloatField from defence360agent.utils import importer MalwareHitStatus = importer.get( module="imav.malwarelib.config", name="MalwareHitStatus", default=None ) logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.add_fields( MalwareHit, status=CharField(default=MalwareHitStatus.FOUND), cleaned_at=FloatField(null=True), ) def rollback(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHit, "status", "cleaned_at") defence360agent/migrations/107_add_bruteforce_rule_33339.py0000644000000000000000000000146400000000000020433 0ustar import logging from defence360agent.contracts.config import ConfigFile KEY = "MOD_SEC_BLOCK_BY_CUSTOM_RULE" logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return # adding brute-force rule to existing config # this is needed until DEFA-689 is done try: config_file = ConfigFile() config = config_file.config_to_dict(normalize=False) mod_sec_block_rules = config.setdefault(KEY, {}) mod_sec_block_rules["33339"] = { "check_period": 120, "max_incidents": 10, } config_file.dict_to_config({KEY: mod_sec_block_rules}) except Exception: logger.exception("Failed to create rule for 33339") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/107_malware_hit_status_field_populate.py0000644000000000000000000000067300000000000022644 0ustar import logging from peewee import BooleanField logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if not fake: MalwareHit = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHit, "restored") def rollback(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.add_fields(MalwareHit, restored=BooleanField(default=False)) defence360agent/migrations/108_feature_management_cleanup_add.py0000644000000000000000000000100200000000000022023 0ustar import logging from peewee import BooleanField logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): FeatureManagementPerms = migrator.orm["feature_management_permissions"] migrator.add_fields( FeatureManagementPerms, cleanup=BooleanField(default=False) ) def rollback(migrator, database, fake=False, **kwargs): FeatureManagementPerms = migrator.orm["feature_management_permissions"] migrator.remove_fields(FeatureManagementPerms, "cleanup") defence360agent/migrations/108_validate_config.py0000644000000000000000000000231500000000000017003 0ustar import logging import os from defence360agent.contracts.config import ( ConfigsValidator, ConfigsValidatorError, LocalConfig, ) logger = logging.getLogger(__name__) # NOTE: # "MOD_SEC_BLOCK_BY_CUSTOM_RULE" keys are validated even if they are strings. # This migration is probably not needed anymore anyway. def migrate(migrator, database, fake=False, **kwargs): if fake: return # adding brute-force rule to existing config # this is needed until DEFA-689 is done try: try: ConfigsValidator.validate_system_config() except ConfigsValidatorError: local_config = LocalConfig() backup_config = local_config.path + ".invalid" os.rename(local_config.path, backup_config) default_config = local_config.config_to_dict() local_config.dict_to_config(default_config) logger.warning( "Invalid config replaced with default one." " Old config save in %s", backup_config, ) except Exception: logger.exception("Failed to replace invalid config with default one") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/109_dos_detector.py0000644000000000000000000000202600000000000016343 0ustar import logging from defence360agent.contracts.config import ( _DOS_DETECTOR_MIN_LIMIT, ConfigFile, ) logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: config_file = ConfigFile() config = config_file.config_to_dict(False) if "DOS" not in config: return if "max_connections" in config["DOS"] and isinstance( config["DOS"]["max_connections"], int ): config["DOS"]["default_limit"] = max( config["DOS"]["max_connections"], _DOS_DETECTOR_MIN_LIMIT ) del config["DOS"]["max_connections"] if "timeout" in config["DOS"]: config["DOS"]["interval"] = config["DOS"]["timeout"] del config["DOS"]["timeout"] config_file.dict_to_config(config, overwrite=True) except Exception: logger.exception("Failed to replace DOS settings") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/110_ignore_list_ip_as_int.py0000644000000000000000000000114100000000000020215 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): if fake: return class IgnoreListNew(pw.Model): ip = pw.CharField(null=False) network_address = pw.IntegerField(null=False) netmask = pw.IntegerField(null=False) version = pw.IntegerField(null=False) class Meta: db_table = "ignore_list_new" primary_key = pw.CompositeKey( "network_address", "netmask", "version" ) migrator.create_model(IgnoreListNew) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/111_ignore_list_ip_as_int.py0000644000000000000000000000245500000000000020227 0ustar import ipaddress def migrate(migrator, database, fake=False, **kwargs): if fake: return IgnoreListNew = migrator.orm["ignore_list_new"] IgnoreList = migrator.orm["ignore_list"] try: from defence360agent.utils.validate import IP from im360.utils.net import pack_ip_network except ImportError: pass else: with database.atomic(): # FIXME: after migrating to peewee 3 add .iterator() ip_strings = [item["ip"] for item in IgnoreList.select().dicts()] ips = set() for item in ip_strings: try: ip = ipaddress.ip_network(item) except ValueError: # malformed ip continue ips.add(ip) for ip in ips: net, mask, version = pack_ip_network(ip) IgnoreListNew.create( ip=IP.ip_net_to_string(ip), network_address=net, netmask=mask, version=version, ) migrator.sql("DROP TABLE ignore_list") migrator.sql("ALTER TABLE ignore_list_new RENAME TO ignore_list") def rollback(migrator, database, fake=False, **kwargs): """Write your rollback migrations here.""" pass defence360agent/migrations/112_hardened_php.py0000644000000000000000000000353400000000000016305 0ustar import contextlib import logging import os import os.path from defence360agent.utils import importer subtract_flags = importer.get( module="imav.malwarelib.utils.chattr", name="subtract_flags", default=None ) FS_IMMUTABLE_FL = importer.get( module="imav.malwarelib.utils.chattr", name="FS_IMMUTABLE_FL", default=None ) logger = logging.getLogger(__name__) ALT_PHP = "imunify360-alt-php.repo" EA_PHP = "imunify360-ea-php-hardened.repo" REPOS_DIR = "/etc/yum.repos.d/" def irrelevant_repos(release): if "cloudlinux" in release: # CloudLinux doesn't need either return {ALT_PHP, EA_PHP} elif os.path.exists("/usr/local/cpanel/cpanel"): # cPanel does not need alt-php return set([ALT_PHP]) else: # ea-php is only for cPanel return set([EA_PHP]) def fix_permissions(): # we don't expect that it can be None with in a way how it imported if subtract_flags is None: return for repo_name in [ALT_PHP, EA_PHP]: path = REPOS_DIR + repo_name if not os.path.exists(path): continue with open(path) as f: subtract_flags(f.fileno(), FS_IMMUTABLE_FL) os.chmod(f.fileno(), 0o644) def do_migrate(): if not os.path.exists("/etc/redhat-release"): # we do not have to do anything on Ubuntu systems return with open("/etc/redhat-release") as f: release = f.read().lower() fix_permissions() for repo_name in irrelevant_repos(release): with contextlib.suppress(FileNotFoundError): os.unlink(REPOS_DIR + repo_name) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: do_migrate() except Exception: logger.exception("Failed to clean up HardenedPHP repositories") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/113_move_quarantined_files.py0000644000000000000000000000015700000000000020406 0ustar """ Quarantine is removed in DEF-15234""" def migrate(*_, **__): pass def rollback(*_, **__): pass defence360agent/migrations/114_disable_auto-quarantine.py0000644000000000000000000000177500000000000020473 0ustar import logging import os from defence360agent.contracts.config import ConfigFile, Core logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): """Write your migrations here.""" if fake: return usernames = [None] if os.path.exists(Core.USER_CONFDIR): usernames.extend(os.listdir(Core.USER_CONFDIR)) for username in usernames: config_file = ConfigFile(username=username) config = config_file.config_to_dict() if not config: continue default_action = config.setdefault("MALWARE_SCANNING", {}).get( "default_action" ) if default_action == "quarantine": config["MALWARE_SCANNING"]["default_action"] = "notify" try: config_file.dict_to_config( config, overwrite=True, validate=False ) except Exception: pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/115_feature_management_fields.py0000644000000000000000000000251600000000000021043 0ustar import peewee as pw from defence360agent.feature_management.constants import NA, FULL, AV_REPORT def migrate(migrator, database, fake=False, **kwargs): permissions_model = migrator.orm["feature_management_permissions"] migrator.add_fields( permissions_model, proactive_new=pw.TextField( default=FULL, null=False, constraints=[ pw.Check("proactive_new in ('{}','{}')".format(NA, FULL)) ], ), av=pw.TextField( default=AV_REPORT, null=False, constraints=[ pw.Check("av in ('{}','{}','{}')".format(NA, AV_REPORT, FULL)) ], ), ) migrator.sql( "UPDATE feature_management_permissions SET av=? WHERE cleanup=1", (FULL,), ) migrator.sql( "UPDATE feature_management_permissions SET av=? WHERE cleanup=0", (AV_REPORT,), ) migrator.sql( "UPDATE feature_management_permissions SET proactive_new=? " "WHERE proactive=1", (FULL,), ) migrator.sql( "UPDATE feature_management_permissions SET proactive_new=? " "WHERE proactive=0", (NA,), ) migrator.remove_fields(permissions_model, "cleanup", "proactive") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/116_feature_management_fields.py0000644000000000000000000000062200000000000021040 0ustar def migrate(migrator, database, fake=False, **kwargs): """ This is final accions for migration 115. For some reason, it does not work if executed in the same migration """ permissions_model = migrator.orm["feature_management_permissions"] migrator.rename_field(permissions_model, "proactive_new", "proactive") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/117_remove_incorrect_fields.py0000644000000000000000000000137100000000000020561 0ustar import logging from defence360agent.contracts.config import IConfig, LocalConfig logger = logging.getLogger(__name__) def _fix_config(config_file): try: config = config_file.config_to_dict(normalize=False) if "DOS" not in config: return config["DOS"].pop("timeout", None) config["DOS"].pop("max_connections", None) config_file.dict_to_config(config, overwrite=True, validate=False) except Exception: logger.exception("Failed to remove fields") def migrate( migrator, database, fake=False, config_file: IConfig = LocalConfig(), **kwargs ): if fake: return _fix_config(config_file) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/118_add_malware_user_infected.py0000644000000000000000000000020300000000000021017 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/118_remove_country_subnets.py0000644000000000000000000000042000000000000020504 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): CountrySubnets = migrator.orm["country_subnets"] migrator.remove_model(CountrySubnets) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/119_populate_malware_user_infected.py0000644000000000000000000000020300000000000022121 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/120_scheduled_scan.py0000644000000000000000000000232400000000000016623 0ustar import logging from datetime import date, timedelta import peewee as pw from defence360agent.contracts.config import ConfigFile from defence360agent.utils import importer MalwareScanType = importer.get( module="imav.malwarelib.config", name="MalwareScanType", default=None ) logger = logging.getLogger(__name__) types = ( MalwareScanType.ON_DEMAND, MalwareScanType.REALTIME, MalwareScanType.MALWARE_RESPONSE, MalwareScanType.BACKGROUND, ) def _update_config(path=None): tomorrow = date.today() + timedelta(days=1) config = { "MALWARE_SCAN_SCHEDULE": { "day_of_month": tomorrow.day, } } try: config_file = ConfigFile(path=path) config_file.dict_to_config(config) except Exception: logger.exception("Failed to set malware scan schedule config") def migrate(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.change_fields( MalwareScan, type=pw.CharField( null=False, constraints=[pw.Check("type in {}".format(types))] ), ) if fake: return _update_config() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/121_drop_captcha_stat.py0000644000000000000000000000026200000000000017341 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.remove_model(migrator.orm["captcha_stat"]) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/122_cagefs_unmount.py0000644000000000000000000000042400000000000016675 0ustar def migrate(migrator, database, fake=False, **kwargs): # We have moved all content from this migration to 123_fixed_cagefs_unmount # in order to re-run this migration because it was corrupted pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/123_add_last_user_scan.py0000644000000000000000000000064000000000000017476 0ustar import peewee as pw class LastUserScan(pw.Model): class Meta: db_table = "last_user_scans" last_scanid = pw.CharField(primary_key=True) started = pw.IntegerField(null=False) uid = pw.IntegerField(null=False) def migrate(migrator, database, fake=False, **kwargs): pass # dropped in DEF-11278 def rollback(migrator, database, fake=False, **kwargs): pass # dropped in DEF-11278 defence360agent/migrations/123_disable_scheduled_scan.py0000644000000000000000000000170200000000000020310 0ustar import contextlib import logging import os from defence360agent.contracts.config import ConfigFile, NONE logger = logging.getLogger(__name__) # Before DEF-35627 the cron file was defined in MalwareScanSchedule.CRON_PATH and was located here CRON_PATH = "/etc/cron.d/imunify_scan_schedule" def _update_config(path=None): config = { "MALWARE_SCAN_SCHEDULE": { "interval": NONE, } } try: config_file = ConfigFile(path=path) config_file.dict_to_config(config) except Exception: logger.exception("Failed to set malware scan schedule config") def _remove_cron(path=CRON_PATH): with contextlib.suppress(FileNotFoundError): os.unlink(path) def migrate(migrator, database, fake=False, **kwargs): # Stubbed in DEF-11010 # if fake: # return # # _update_config() # _remove_cron() pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/123_rename_plesk_vendor.py0000644000000000000000000000160700000000000017707 0ustar import logging from defence360agent.utils import run_coro, antivirus_mode logger = logging.getLogger(__name__) @antivirus_mode.skip def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from im360.subsys.panels.plesk import Plesk from im360.subsys.panels.plesk.mod_security import ( plesk_supports_custom_vendors, ) except ImportError: return try: if Plesk.is_installed() and run_coro(plesk_supports_custom_vendors()): panel = Plesk() installed_vendors = run_coro(panel.modsec_vendor_list()) if "imunify360" in " ".join(installed_vendors): run_coro(panel.install_settings()) except Exception as e: logger.warning('Unable to reinstall modsec "custom" ruleset: %s', e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/124_add_hook_management_functionality.py0000644000000000000000000000117600000000000022603 0ustar from time import time from peewee import Model, CharField, IntegerField, BooleanField from defence360agent.model.simplification import FilenameField class EventHook(Model): class Meta: db_table = "event_hook" path = FilenameField(null=False) event = CharField(null=False) created = IntegerField(null=False, default=lambda: int(time())) native = BooleanField(default=False) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(EventHook) def rollback(migrator, database, fake=False, **kwargs): EventHook = migrator.orm["event_hook"] migrator.remove_model(EventHook) defence360agent/migrations/124_add_infected_domains_vendor.py0000644000000000000000000000065100000000000021344 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): InfectedDomains = migrator.orm["infected_domain_list"] migrator.add_fields( InfectedDomains, vendor=pw.TextField(null=False, default="google-safe-browsing"), ) InfectedDomains.delete().execute() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/125_rescan_scan_type.py0000644000000000000000000000251700000000000017210 0ustar import logging from datetime import datetime, timedelta import peewee as pw from defence360agent.utils import importer from defence360agent.utils import split_for_chunk MalwareScanType = importer.get( module="imav.malwarelib.config", name="MalwareScanType", default=None ) logger = logging.getLogger(__name__) types = ( MalwareScanType.ON_DEMAND, MalwareScanType.REALTIME, MalwareScanType.MALWARE_RESPONSE, MalwareScanType.BACKGROUND, MalwareScanType.RESCAN, ) def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] MalwareScan = migrator.orm["malware_scans"] date = (datetime.now() - timedelta(days=30)).timestamp() hits_to_delete = list( MalwareHit.select(MalwareHit.id) .join(MalwareScan) .where(MalwareScan.started < date) ) for chunk in split_for_chunk(hits_to_delete): sql, params = MalwareHit.delete().where(MalwareHit.id.in_(chunk)).sql() migrator.sql(sql, params) sql, params = MalwareScan.delete().where(MalwareScan.started < date).sql() migrator.sql(sql, params) migrator.change_fields( MalwareScan, type=pw.CharField( null=False, constraints=[pw.Check("type in {}".format(types))] ), ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/126_add_malware_scan_modified_files_option.py0000644000000000000000000000136200000000000023544 0ustar import os import yaml from defence360agent.contracts.config import IConfigFile, LocalConfig from defence360agent.utils import log_error_and_ignore @log_error_and_ignore() def migrate( migrator, database, fake=False, config_file: IConfigFile = LocalConfig(), **kwargs ): if fake: return if not os.path.exists(config_file.path): return with open(config_file.path) as f: conf = yaml.safe_load(f) malware_settings = conf.setdefault("MALWARE_SCANNING", {}) value = malware_settings.pop("scan_modified_files", None) malware_settings["scan_modified_files"] = value config_file.dict_to_config(conf, validate=False) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/126_move_malware_hits_list.py0000644000000000000000000000207200000000000020425 0ustar import logging import shutil from defence360agent.files import FILES_DIR from defence360agent.utils import importer, antivirus_mode logger = logging.getLogger(__name__) def _move(src, dst): try: shutil.move(src, dst) except FileNotFoundError: pass except Exception as err: logger.error( "Failed to move HackerTrap list to the new location: %r", err ) @antivirus_mode.skip def migrate(migrator, database, fake=False, **kwargs): if fake: return try: from defence360agent.contracts.config import HackerTrap HackerTrapHitsSaver = importer.get( module="imav.malwarelib.subsys.malware", name="HackerTrapHitsSaver", default=None, ) except ImportError: return HackerTrapHitsSaver.BASE_DIR = str(FILES_DIR) src1 = HackerTrapHitsSaver._filepath() src2 = HackerTrapHitsSaver._clean_filepath() for src in src1, src2: _move(str(src), HackerTrap.DIR) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/127_remove_malware_hit_mode.py0000644000000000000000000000032700000000000020544 0ustar def migrate(migrator, database, fake=False, **kwargs): MalwareHit = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHit, "mode") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/128_move_cleanup_storage_files.py0000644000000000000000000000337200000000000021256 0ustar import logging import os import shutil from peewee import CharField, Model from defence360agent.utils import importer from defence360agent.model.simplification import FilenameField CleanupStorage = importer.get( module="imav.malwarelib.cleanup.storage", name="CleanupStorage", default=None, ) logger = logging.getLogger(__name__) def get_model(db): """ Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField """ class MalwareHit(Model): class Meta: db_table = "malware_hits" database = db user = CharField(null=False) orig_file = FilenameField(null=False) hash = CharField(null=True) size = CharField(null=True) @property def storage_name(self) -> str: """ Get file name for cleanup storage :return: file name """ try: return os.path.extsep.join([self.user, self.hash, self.size]) except TypeError: return None return MalwareHit def _move(src, dst): src, dst = map(CleanupStorage.path.joinpath, (src, dst)) src, dst = map(str, (src, dst)) try: shutil.move(src, dst) except FileNotFoundError: pass except Exception as err: logger.error("Failed to move stored file to the new location: %r", err) def migrate(migrator, database, fake=False, **kwargs): if fake: return MalwareHit = get_model(database) for hit in MalwareHit: src = hit.storage_name if src is None: continue dst = CleanupStorage.storage_name(hit.orig_file) _move(src, dst) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/129_fixed_cagefs_unmount.py0000644000000000000000000000255400000000000020071 0ustar import subprocess import time import os from functools import lru_cache from defence360agent.utils import retry_on, run_with_umask _SERVICE_NAME = "cagefs" _COMMAND = "restart" _CAGEFSCTL_TOOL = "/usr/sbin/cagefsctl" _WAIT_LOCK = "--wait-lock" @lru_cache(1) def systemctl_present(paths=["/usr/bin", "/bin"]): """Return whether we can find systemctl in given *paths*.""" return any(os.path.isfile(os.path.join(p, "systemctl")) for p in paths) def _restart_cagefs(exc, i): if systemctl_present(): cmd = ["systemctl", _COMMAND, _SERVICE_NAME] else: cmd = ["service", _SERVICE_NAME, _COMMAND] try: subprocess.check_call(cmd) except Exception: pass time.sleep(5) @retry_on( subprocess.CalledProcessError, max_tries=3, on_error=_restart_cagefs, silent=True, ) def _execute_command(cmd): subprocess.check_output(cmd, shell=False, stderr=subprocess.STDOUT) def migrate(migrator, database, fake=False, umask=0o022, **kwargs): if fake: return cmd_list = [ [_CAGEFSCTL_TOOL, _WAIT_LOCK, "--force-update-etc"], [_CAGEFSCTL_TOOL, _WAIT_LOCK, "--remount-all"], ] with run_with_umask(umask): if os.path.exists(_CAGEFSCTL_TOOL): for cmd in cmd_list: _execute_command(cmd) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/130_add_messages_to_send.py0000644000000000000000000000071000000000000020007 0ustar from peewee import FloatField, Model, BlobField class MessageToSend(Model): class Meta: db_table = "messages_to_send" timestamp = FloatField(null=False) message = BlobField(null=False) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(MessageToSend) def rollback(migrator, database, fake=False, **kwargs): MessageToSend = migrator.orm["messages_to_send"] migrator.drop_model(MessageToSend) defence360agent/migrations/131_incident_timestamp_index.py0000644000000000000000000000046100000000000020730 0ustar # Add index on timestamp field to incident table. Helps to speed up queries. def migrate(migrator, database, fake=False, **kwargs): migrator.sql( "CREATE INDEX IF NOT EXISTS incident_timestamp ON incident (timestamp)" ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/132_add_timestamp_field.py0000644000000000000000000000063200000000000017640 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.add_fields(MalwareHits, timestamp=pw.FloatField(null=True)) def rollback(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "timestamp") defence360agent/migrations/133_add_scope_field_to_iplist.py0000644000000000000000000000110700000000000021033 0ustar import logging import peewee as pw logger = logging.getLogger(__name__) SCOPE_LOCAL, SCOPE_GROUP = "local", "group" def migrate(migrator, database, fake=False, **kwargs): ip_list = migrator.orm["iplist"] migrator.add_fields( ip_list, scope=pw.CharField( null=True, constraints=[ pw.Check("scope in ('%s','%s')" % (SCOPE_LOCAL, SCOPE_GROUP)) ], ), ) def rollback(migrator, database, fake=False, **kwargs): ip_list = migrator.orm["iplist"] migrator.remove_fields(ip_list, "scope") defence360agent/migrations/134_change_default_of_intensity_ram.py0000644000000000000000000000112700000000000022246 0ustar import logging from defence360agent.contracts.config import ConfigFile logger = logging.getLogger(__name__) def _update_config(path=None): config = { "MALWARE_SCAN_INTENSITY": { "ram": 2048, } } try: config_file = ConfigFile(path=path) config_file.dict_to_config(config) except Exception: logger.exception("Failed to set malware scan schedule config") def migrate(migrator, database, fake=False, **kwargs): if fake: return _update_config() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/135_export_proactive.py0000644000000000000000000000264100000000000017264 0ustar import csv import os import logging logger = logging.getLogger(__name__) PROACTIVE_CSV, PROACTIVE_ENV_CSV = "proactive.csv", "proactive_env.csv" PROACTIVE_SQL = """SELECT id, timestamp, ip, ip_int, ip_version, ip_country_id, description, action, host, path, url, count, uid, gid, rule_id, rule_name FROM proactive ORDER BY timestamp DESC LIMIT ?""" PROACTIVE_ENV_SQL = """ SELECT proactive_env.event_id, proactive_env.name, proactive_env.value FROM proactive_env INNER JOIN ({}) pa ON proactive_env.event_id=pa.id """.format( PROACTIVE_SQL ) EXPORT_DIR = "/var/lib/imunify360-php-daemon/export" def export(database, target_dir, events_num): for filename, query in [ (PROACTIVE_CSV, PROACTIVE_SQL), (PROACTIVE_ENV_CSV, PROACTIVE_ENV_SQL), ]: cur = database.execute_sql(query, (events_num,)) with open( os.path.join(target_dir, filename), "w", newline="", encoding="utf-8", ) as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerows(cur) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: os.makedirs(EXPORT_DIR, exist_ok=True) export(database, EXPORT_DIR, 1000) except Exception: # not critical logger.exception("Failed to export proactive defence data") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/135_make_completed_nullable.py0000644000000000000000000000047200000000000020516 0ustar def migrate(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.drop_not_null(MalwareScan, "completed") def rollback(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.add_not_null(MalwareScan, "completed") defence360agent/migrations/136_drop_proactive.py0000644000000000000000000000035000000000000016703 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.remove_model(migrator.orm["proactive"]) migrator.remove_model(migrator.orm["proactive_env"]) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/137_swap_initiator_and_cause.py0000644000000000000000000000100000000000000020713 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHistory = migrator.orm["malware_history"] try: for entry in MalwareHistory.select(): if entry.initiator in ["manual", "on-demand", "realtime"]: entry.cause, entry.initiator = entry.initiator, entry.cause entry.save() except Exception as e: logger.exception(e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/138_move_rapid_scan_dir.py0000644000000000000000000000312600000000000017660 0ustar import asyncio import pathlib import shutil from defence360agent.subsys.panels import hosting_panel from defence360agent.utils import importer panel_users = importer.get( module="imav.malwarelib.utils.user_list", name="panel_users", default=None ) def migrate(migrator, database, fake=False, **kwargs): if fake: return loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) users = loop.run_until_complete(panel_users()) for user in users: path_obj = pathlib.Path(user["home"]) old_path = str(path_obj.parent / ".rapid-scan-db" / path_obj.name) try: new_path = hosting_panel.HostingPanel().get_rapid_scan_db_dir( user["home"] ) except OSError: continue if new_path is None or old_path == new_path: continue try: shutil.move(old_path, new_path) except OSError: pass def rollback(migrator, database, fake=False, **kwargs): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) users = loop.run_until_complete(panel_users()) for user in users: path_obj = pathlib.Path(user["home"]) old_path = str(path_obj.parent / ".rapid-scan-db" / path_obj.name) try: new_path = hosting_panel.HostingPanel().get_rapid_scan_db_dir( user["home"] ) except OSError: continue if new_path is None or old_path == new_path: continue try: shutil.move(new_path, old_path) except OSError: pass defence360agent/migrations/139_generic_modsec_config.py0000644000000000000000000000043500000000000020165 0ustar def migrate(migrator, database, fake=False, **kwargs): """ Rely on install-vendors to update modsec.conf on the 1st install. Drop support for updating old imunify360 versions without modsec.conf.d/ """ def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.py0000644000000000000000000000044300000000000022667 0ustar def migrate(migrator, database, fake=False, **kwargs): if fake: return migrator.sql( "UPDATE malware_hits SET orig_file = CAST(orig_file AS BLOB) " 'WHERE typeof(orig_file) != "blob";' ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/141_drop_last_user_scans.py0000644000000000000000000000034300000000000020075 0ustar def migrate(migrator, database, fake=False, **kwargs): if "last_user_scans" in migrator.orm: migrator.remove_model(migrator.orm["last_user_scans"]) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/143_malware_hit_cascade_delete.py0000644000000000000000000000277300000000000021155 0ustar import peewee class MalwareScan(peewee.Model): class Meta: db_table = "malware_scans" scanid = peewee.CharField(primary_key=True) class MalwareHit(peewee.Model): class Meta: db_table = "malware_hits" id = peewee.PrimaryKeyField() scanid = peewee.ForeignKeyField( MalwareScan, null=False, related_name="hits", on_delete="CASCADE" ) user = peewee.CharField(null=False) orig_file = peewee.BlobField(null=False) type = peewee.CharField(null=False) malicious = peewee.BooleanField(null=False, default=False) vendor = peewee.CharField(null=False, default="ai-bolit") hash = peewee.CharField(null=True) size = peewee.CharField(null=True) timestamp = peewee.FloatField(null=True) status = peewee.CharField(default="found") cleaned_at = peewee.FloatField(null=True) @classmethod def get_field_names(cls): return map(lambda field: field.column_name, cls._meta.sorted_fields) def migrate(migrator, database, fake=False, **kwargs): migrator.sql("ALTER TABLE malware_hits RENAME TO malware_hits_old;") migrator.create_model(MalwareHit) # specify fields order directly, because '*' doesnt guarantee order malware_hit_fields = ",".join(MalwareHit.get_field_names()) migrator.sql( "INSERT INTO malware_hits ({0}) SELECT {0} FROM malware_hits_old;" .format(malware_hit_fields) ) migrator.sql("DROP TABLE malware_hits_old;") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/144_remove_clamav_config_options.py0000644000000000000000000000146500000000000021612 0ustar import logging from defence360agent.contracts.config import IConfig, ConfigFile logger = logging.getLogger(__name__) def migrate( migrator, database, fake=False, config_file: IConfig = ConfigFile(), **kwargs ): if fake: return try: config = config_file.config_to_dict(normalize=False) if "MALWARE_SCANNING" not in config: return config["MALWARE_SCANNING"].pop("i360_clamd", None) config["MALWARE_SCANNING"].pop("show_clamav_results", None) config["MALWARE_SCANNING"].pop("clamav_binary", None) config_file.dict_to_config(config, overwrite=True, validate=False) except Exception: logger.exception("Failed to remove clamav config options") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/144_remove_hash_table.py0000644000000000000000000000026100000000000017332 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.sql("DROP TABLE IF EXISTS malware_hash;") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/145_move_quarantine.py0000644000000000000000000000015700000000000017065 0ustar """ Quarantine is removed in DEF-15234""" def migrate(*_, **__): pass def rollback(*_, **__): pass defence360agent/migrations/146_malware_user_infected_cascade_delete.py0000644000000000000000000000020300000000000023175 0ustar def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/147_remove_vendor_field.py0000644000000000000000000000060100000000000017701 0ustar import peewee def migrate(migrator, database, fake=False, **kwargs): malware_hits = migrator.orm["malware_hits"] migrator.remove_fields(malware_hits, "vendor") def rollback(migrator, database, fake=False, **kwargs): malware_hits = migrator.orm["malware_hits"] migrator.add_fields( malware_hits, vendor=peewee.CharField(null=False, default="ai-bolit") ) defence360agent/migrations/147_user_scan_type.py0000644000000000000000000000130700000000000016713 0ustar import peewee as pw from defence360agent.utils import importer MalwareScanType = importer.get( module="imav.malwarelib.config", name="MalwareScanType", default=None ) types = ( MalwareScanType.ON_DEMAND, MalwareScanType.REALTIME, MalwareScanType.MALWARE_RESPONSE, MalwareScanType.BACKGROUND, MalwareScanType.RESCAN, MalwareScanType.USER, ) def migrate(migrator, database, fake=False, **kwargs): MalwareScan = migrator.orm["malware_scans"] migrator.change_fields( MalwareScan, type=pw.CharField( null=False, constraints=[pw.Check("type in {}".format(types))] ), ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/148_reconstruct_pickled_scan_queue.py0000644000000000000000000000040600000000000022146 0ustar def migrate(*_, **__): """ Backward compatibility for reconstruction of pickled scan queue is done in the imav.malwarelib.scan.queue.py module. Migration is no longer needed. """ def rollback(*_, **__): """Downgrade is not supported""" defence360agent/migrations/148_remove_malware_user_infected.py0000644000000000000000000000027100000000000021574 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.sql("DROP TABLE IF EXISTS malware_user_infected") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/149_add_captcha_passed_field_to_iplist.py0000644000000000000000000000055500000000000022701 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.add_fields( IPList, captcha_passed=pw.BooleanField(null=False, default=False) ) def rollback(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] migrator.remove_fields(IPList, "captcha_passed") defence360agent/migrations/149_make_config_inactive.py0000644000000000000000000000161300000000000020016 0ustar """ This migration is needed to cleanup modsec config on cPanel by removing includes for modsec2.imunify.conf File is automatically included from /etc/apache2/conf.d, thus no explicit includes are needed """ import logging import subprocess logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return for conf in ["includes/modsec2.imunify.conf", "modsec2.imunify.conf"]: try: subprocess.run( [ "/usr/sbin/whmapi1", "modsec_make_config_inactive", "config={}".format(conf), ], check=True, ) except FileNotFoundError: pass except Exception: logger.exception("Failed to make %s inactive", conf) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.py0000644000000000000000000000120200000000000025306 0ustar import logging logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): IPList = migrator.orm["iplist"] captcha_pass_condition = ( (IPList.listname == "WHITE") & (~IPList.full_access) & (~IPList.manual) & (IPList.comment.contains("due to successful captcha pass")) ) try: q = IPList.update({IPList.captcha_passed: True}).where( captcha_pass_condition ) q.execute() except Exception: logger.exception("Failed update to captcha_passed field") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/151_change_constraint_for_iplist.py0000644000000000000000000000074400000000000021612 0ustar import peewee as pw def migrate(migrator, database, fake=False, **kwargs): orm_IPList = migrator.orm["iplist"] IP_LISTS = ("WHITE", "BLACK", "GRAY", "GRAY_SPLASHSCREEN") migrator.change_fields( orm_IPList, listname=pw.CharField( null=False, constraints=[ pw.Check("listname in ('{}')".format("','".join(IP_LISTS))) ], ), ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/152_add_listname_to_primary_key.py0000644000000000000000000000631700000000000021433 0ustar from peewee import ( BooleanField, CharField, Check, CompositeKey, ForeignKeyField, IntegerField, Model, ) import time def migrate(migrator, database, fake=False, **kwargs): orm_IPList = migrator.orm["iplist"] Country = migrator.orm["country"] class TMP_IPList(Model): """'iplist' db table.""" #: field name ACTION_TYPE = "action_type" #: available list names IP_LISTS = [WHITE, BLACK, GRAY, GRAY_SPLASHSCREEN] = ( "WHITE", "BLACK", "GRAY", "GRAY_SPLASHSCREEN", ) SCOPE_LOCAL, SCOPE_GROUP = "local", "group" ip = CharField(null=False) listname = CharField( null=False, constraints=[ Check("listname in ('{}')".format("','".join(IP_LISTS))) ], ) # null=True to be consistent # with previously used create table sql expiration = IntegerField( default=0, null=True # 0 - never ) # null - the same :( imported_from = CharField(null=True) ctime = IntegerField( null=True, default=lambda: int(time.time()) # those ) # are OK deep = IntegerField(null=True) comment = CharField(null=True) country = ForeignKeyField(Country, null=True) # actual for not manually whitelisted ips only # should be ignored for others captcha_passed = BooleanField(null=False, default=False) # available only for graylist manual = BooleanField(null=False, default=True) # available only for whitelist full_access = BooleanField(null=True) # was IP autowhitelisted with `--remote-addr` flag or not auto_whitelisted = BooleanField(null=True, default=False) network_address = IntegerField(null=False) netmask = IntegerField(null=False) version = IntegerField(null=False) scope = CharField( null=True, constraints=[ Check("scope in ('%s','%s')" % (SCOPE_LOCAL, SCOPE_GROUP)) ], ) class Meta: db_table = "tmpiplist" primary_key = CompositeKey( "network_address", "netmask", "version", "listname" ) migrator.create_model(TMP_IPList) # we can't use migrator.rename_table due to bug in peewee_migrate # https://github.com/klen/peewee_migrate/pull/158 # # Also, sqlite could mix fields in command # insert into table select * from other_table # https://stackoverflow.com/questions/56682520/copy-sqlite-table-with-mixed-column-order # fields = [ name for name in TMP_IPList._meta.sorted_field_names if name != "country" ] + ["country_id"] migrator.sql( "INSERT INTO tmpiplist ({fields}) SELECT {fields} FROM iplist".format( fields=",".join(fields) ) ) migrator.sql("DROP TABLE iplist") migrator.sql("ALTER TABLE tmpiplist RENAME TO iplist") migrator.add_index(orm_IPList, "listname") migrator.add_index(orm_IPList, "expiration") migrator.add_index(orm_IPList, "ip") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/153_migrate_config_default_action.py0000644000000000000000000000310600000000000021702 0ustar import logging import os from defence360agent.contracts.config import ConfigFile, IConfig from defence360agent.utils import log_error_and_ignore logger = logging.getLogger(__name__) def migrate( migrator, database, user_config_dir="/etc/imunify360/user_config", config_file: IConfig = ConfigFile(), fake=False, **kwargs ): if fake: return # Migrate root config migrate_config(config_file) if not os.path.exists(user_config_dir): return # Migrate user configs for username in os.listdir(user_config_dir): migrate_config(ConfigFile(username=username)) @log_error_and_ignore() def migrate_config(config_file: IConfig): config = config_file.config_to_dict(normalize=False) if not config: return malware_settings = config.setdefault("MALWARE_SCANNING", {}) default_action = malware_settings.get("default_action") if default_action == "quarantine": malware_settings["default_action"] = "cleanup" cleanup_settings = config.setdefault("MALWARE_CLEANUP", {}) keep_original_files = cleanup_settings.get("keep_original_files_days") if keep_original_files is not None and keep_original_files < 180: cleanup_settings["keep_original_files_days"] = 180 elif default_action in ("cleanup_or_quarantine", "delete"): malware_settings["default_action"] = "cleanup" else: return config_file.dict_to_config( config, overwrite=True, validate=False, normalize=False ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/153_update_incident_name.py0000644000000000000000000000037400000000000020027 0ustar def migrate(migrator, database, fake=False, **kwargs): migrator.sql( "UPDATE incident SET name='Login Blocked by cpHulk'" " where plugin='cphulk' and name=''" ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/154_migrate_config_user_override_malware_actions.py0000644000000000000000000000143500000000000025032 0ustar from defence360agent.contracts.config import ( IConfig, LocalConfig, NonBaseMerger, ) def migrate(*_, config_file: IConfig = LocalConfig(), fake=False, **__): if fake: return config = NonBaseMerger( names=(NonBaseMerger.get_layer_names()) ).configs_to_dict(force_read=True) permission_settings = config.setdefault("PERMISSIONS", {}) user_override_malware_actions = permission_settings.get( "user_override_malware_actions" ) if user_override_malware_actions is None: permission_settings["user_override_malware_actions"] = True config_file.dict_to_config( {"PERMISSIONS": permission_settings}, validate=False, without_defaults=True, ) def rollback(*_, **__): pass defence360agent/migrations/155_migrate_config_user_override_proactive_defense.py0000644000000000000000000000144100000000000025345 0ustar from defence360agent.contracts.config import ( IConfig, LocalConfig, NonBaseMerger, ) def migrate(*_, config_file: IConfig = LocalConfig(), fake=False, **__): if fake: return config = NonBaseMerger( names=(NonBaseMerger.get_layer_names()) ).configs_to_dict(force_read=True) permission_settings = config.setdefault("PERMISSIONS", {}) user_override_malware_actions = permission_settings.get( "user_override_proactive_defense" ) if user_override_malware_actions is None: permission_settings["user_override_proactive_defense"] = True config_file.dict_to_config( {"PERMISSIONS": permission_settings}, validate=False, without_defaults=True, ) def rollback(*_, **__): pass defence360agent/migrations/156_remove_default_values_from_config.py0000644000000000000000000000113100000000000022613 0ustar """ Remove values from imunify360.config that are the same as in imunify360-base.config. Use stub migration, since for new installations imunify360-base.config is absent, so this migration is no longer needed in this case. Otherwise, when the migration has already been applied, no need to reapply. Keep the migration itself, since it was already released. To remove schema defaults from imunify360.config 159_remove_defaults_from_local_config migration is used. """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/157_move_i360_modsec_disable_conf.py0000644000000000000000000000223100000000000021417 0ustar import logging import os import shutil from defence360agent.utils import OsReleaseInfo logger = logging.getLogger(__name__) _DEBIAN_NEW_MODSEC_DISABLE_FILENAME = ( "/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.conf" ) _DEBIAN_OLD_MODSEC_DISABLE_FILENAME = ( "/etc/apache2/conf/plesk.conf.d/vhosts/i360_modsec_disable.conf" ) _NEW_MODSEC_DISABLE_FILENAME = ( "/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.conf" ) _OLD_MODSEC_DISABLE_FILENAME = ( "/etc/httpd/conf/plesk.conf.d/vhosts/i360_modsec_disable.conf" ) def migrate(migrator, database, fake=False, **kwargs): if fake: return if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: old_file_name = _DEBIAN_OLD_MODSEC_DISABLE_FILENAME new_file_name = _DEBIAN_NEW_MODSEC_DISABLE_FILENAME else: old_file_name = _OLD_MODSEC_DISABLE_FILENAME new_file_name = _NEW_MODSEC_DISABLE_FILENAME if os.path.exists(old_file_name): try: shutil.move(old_file_name, new_file_name) except Exception: logger.exception("Failed move %s", old_file_name) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/158_move_i360_modsec_disable_conf_symlink.py0000644000000000000000000000330600000000000023172 0ustar import logging import os import shutil from defence360agent.utils import OsReleaseInfo logger = logging.getLogger(__name__) _DEBIAN_MODSEC_DISABLE_SYMLINK_PATH = ( "/etc/apache2/plesk.conf.d/i360_modsec_disable.conf" ) _DEBIAN_MODSEC_DISABLE_SYMLINK = ( "/etc/apache2/conf-enabled/zz999_modsec2.imunify_disable.conf" ) _MODSEC_DISABLE_SYMLINK_PATH = ( "/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.conf" ) _MODSEC_DISABLE_SYMLINK = ( "/etc/httpd/conf.d/zz999_modsec2.imunify_disable.conf" ) _DEBIAN_NEW_MODSEC_DISABLE_FILENAME = ( "/etc/apache2/plesk.conf.d/i360_modsec_disable.conf" ) _DEBIAN_OLD_MODSEC_DISABLE_FILENAME = ( "/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.conf" ) def migrate(migrator, database, fake=False, **kwargs): if fake: return old_file_name = None new_file_name = None if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: old_file_name = _DEBIAN_OLD_MODSEC_DISABLE_FILENAME new_file_name = _DEBIAN_NEW_MODSEC_DISABLE_FILENAME symlink_path = _DEBIAN_MODSEC_DISABLE_SYMLINK_PATH symlink = _DEBIAN_MODSEC_DISABLE_SYMLINK else: symlink_path = _MODSEC_DISABLE_SYMLINK_PATH symlink = _MODSEC_DISABLE_SYMLINK if old_file_name and os.path.exists(old_file_name): try: shutil.move(old_file_name, new_file_name) except Exception: logger.exception("Failed move %s", old_file_name) if os.path.islink(symlink): try: os.unlink(symlink) os.symlink(symlink_path, symlink) except Exception: logger.exception("Failed change symlink %s", symlink) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/159_remove_defaults_from_local_config.py0000644000000000000000000000162500000000000022604 0ustar """ Remove all default values from main config (/etc/sysconfig/imunify360/imunify360.config). See DEF-17214 for details. """ import logging from defence360agent.contracts.config import LocalConfig from defence360agent.contracts.config_provider import exclude_equals logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: local_config = LocalConfig() local_conf = local_config.config_to_dict(force_read=True) defaults = local_config.normalize({}, without_defaults=False) non_default_conf = exclude_equals( main_conf=local_conf, base_conf=defaults ) local_config.dict_to_config(non_default_conf, overwrite=True) except Exception as exc: logger.error("Can't overwrite local config, reason: %s", exc) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/160_remove_quarantine.py0000644000000000000000000000575000000000000017415 0ustar import logging import os import pwd import shutil from glob import glob from pathlib import Path from typing import Tuple, Union from peewee import CharField, Model # Avoiding imav.malwarelib.utils.quar_fileops imports from defence360agent.model.simplification import FilenameField from defence360agent.subsys.panels.hosting_panel import HostingPanel logger = logging.getLogger(__name__) QUAR_NAME = ".imunify.quarantined" DEF_QUAR = "/var/imunify360" QUARANTINED = "quarantined" QUARANTINE_PARENTS = [DEF_QUAR, "/var/www", "/home*"] def get_model(db): """ Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField """ class MalwareHit(Model): class Meta: db_table = "malware_hits" database = db orig_file = FilenameField(null=False) status = CharField() return MalwareHit def migrate(_migrator, database, fake=False, delete_function=None, *_, **__): if fake: return # For unit-tests delete_function = delete_function or delete_quarantine_folder model = get_model(database) quarantined = model.select().where(model.status == QUARANTINED) # Remove all known quarantine storages for hit in quarantined: path_to_delete, _ = find_quar(hit.orig_file) delete_function(path_to_delete) hit.delete_instance() # Remove possible quarantine storages for parent in QUARANTINE_PARENTS: for path_to_delete in glob(os.path.join(parent, QUAR_NAME)): delete_function(path_to_delete) def rollback(*_, **__): pass def delete_quarantine_folder(quarantine_path: Union[str, Path]): quarantine_path = Path(quarantine_path) if ( quarantine_path.name == QUAR_NAME and quarantine_path == quarantine_path.resolve() ): logger.info("Deleting quarantine folder %s", quarantine_path) shutil.rmtree(quarantine_path, ignore_errors=True) def find_quar(source: str) -> Tuple[Path, Path]: """ Find file in quarantine by source path. This function is copied from agent code since it is to be removed. """ file = Path(source) default_result = Path(DEF_QUAR) / QUAR_NAME, file.relative_to(Path("/")) user = None parent = None for path in file.parents: try: user = pwd.getpwuid(path.stat().st_uid) except FileNotFoundError: continue except KeyError: return default_result else: parent = path break # Prevent storing quarantine in '/' if user is None or user.pw_name == "root": return default_result resolved_place = parent.resolve() / file.relative_to(parent) try: base_dir = HostingPanel().base_home_dir(user.pw_dir) except (FileNotFoundError, RuntimeError): return default_result try: relative = resolved_place.relative_to(base_dir) except ValueError: return default_result return base_dir / QUAR_NAME, relative defence360agent/migrations/160_unmount_sigs_v1.py0000644000000000000000000000273600000000000017032 0ustar """Unmount sigs/v1 from CageFS.""" import logging import subprocess from pathlib import Path logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): # NOSONAR python:S1142 if fake: return try: from defence360agent.subsys import clcagefs filename = clcagefs.CAGEFS_MP_FILENAME except ImportError: filename = "/etc/cagefs/cagefs.mp" try: text = Path(filename).read_text() except FileNotFoundError: # indication of a non-cagefs system return # nothing to do except Exception as e: # NOSONAR pylint:W0703 logger.exception("Can't read %s, reason: %s", filename, e) return else: if "/var/imunify360/files/sigs/v1" not in text: return # nothing to do try: subprocess.check_call( r"sed -i.im360-bak '\:/var/imunify360/files/sigs/v1:d' %s" " && grep /var/imunify360/files/sigs/v1 /proc/mounts" " | awk '{ print $2 }' | xargs -rn1 umount" " && /usr/sbin/cagefsctl --wait-lock --unmount-all" " && /usr/sbin/cagefsctl --wait-lock --force-update-etc" " && /usr/sbin/cagefsctl --wait-lock --remount-all" % (filename,), shell=True, executable="/bin/bash", ) except Exception as e: # NOSONAR pylint:W0703 logger.exception("Can't unmount sigs/v1, reason: %s", e) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/161_remove_ea4_main_local_conf.py0000644000000000000000000000320300000000000021072 0ustar """ Remove /var/cpanel/templates/apache2_4/ea4_main.local file introduced by imunify360. This file was used to change apache log format (%h->%a), when imunify360 installed remote_ip apache module. Since this file is created once it can be outdated after updating cPanel ( in case if /var/cpanel/templates/apache2_4/ea4_main.default also updated). See DEF-9641 for details. """ import logging from pathlib import Path import subprocess from defence360agent.utils import antivirus_mode logger = logging.getLogger(__name__) EA4_MAIN_LOCAL_PATH = Path("/var/cpanel/templates/apache2_4/ea4_main.local") EA4_MAIN_DEFAULT_PATH = Path( "/var/cpanel/templates/apache2_4/ea4_main.default" ) NEW = "%a " OLD = "%h " @antivirus_mode.skip def migrate( migrator, database, fake=False, default_conf_path=EA4_MAIN_DEFAULT_PATH, local_conf_path=EA4_MAIN_LOCAL_PATH, **kwargs ): if fake: return try: from im360.subsys.panels.cpanel import cPanel except ImportError: return try: if cPanel.is_installed() and local_conf_path.exists(): origin_text = default_conf_path.read_text() restored_text = local_conf_path.read_text().replace(NEW, OLD) # assume that these changes were made by imunify360 if restored_text == origin_text: # remove file and rebuild confs local_conf_path.unlink() subprocess.check_call(cPanel.REBUILD_HTTPDCONF_CMD) except Exception as exc: logger.error("Can't remove %s, reason: %s", local_conf_path, exc) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/162_add_resource_type.py0000644000000000000000000000345000000000000017366 0ustar import logging import peewee as pw from defence360agent.utils import importer MalwareScanResourceType = importer.get( module="imav.malwarelib.config", name="MalwareScanResourceType", default=None, ) logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.add_fields( MalwareHits, resource_type=pw.CharField( null=False, default=MalwareScanResourceType.FILE.value, constraints=[ pw.Check( "resource_type in {}".format( ( MalwareScanResourceType.DB.value, MalwareScanResourceType.FILE.value, ) ) ) ], ), app_name=pw.CharField(null=True), db_host=pw.CharField(null=True), db_port=pw.CharField(null=True), db_name=pw.CharField(null=True), ) MalwareScan = migrator.orm["malware_scans"] migrator.add_fields( MalwareScan, resource_type=pw.CharField( null=False, default=MalwareScanResourceType.FILE.value, constraints=[ pw.Check( "resource_type in {}".format( ( MalwareScanResourceType.DB.value, MalwareScanResourceType.FILE.value, ) ) ) ], ), ) migrator.rename_field(MalwareScan, "total_files", "total_resources") def rollback(migrator, database, fake=False, **kwargs): MalwareHits = migrator.orm["malware_hits"] migrator.remove_fields(MalwareHits, "resource_type") defence360agent/migrations/163_drop_malware_scanned_stat.py0000644000000000000000000000037700000000000021076 0ustar def migrate(migrator, database, fake=False, **kwargs): try: model = migrator.orm["malware_scanned_stat"] migrator.remove_model(model) except KeyError: pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/164_add_resource_type_to_ignore.py0000644000000000000000000000217300000000000021436 0ustar import time from peewee import CharField, Check, CompositeKey, IntegerField, Model class TMPMalwareIgnorePath(Model): class Meta: db_table = "tmp_malware_ignore_path" primary_key = CompositeKey("path", "resource_type") CACHE = None path = CharField() resource_type = CharField( null=False, constraints=[Check("resource_type in ('file','db')")] ) added_date = IntegerField(null=False, default=lambda: int(time.time())) def migrate(migrator, *_, fake=False, **__): change_malware_ignore_path_model(migrator) def change_malware_ignore_path_model(migrator): migrator.create_model(TMPMalwareIgnorePath) migrator.sql( "INSERT INTO tmp_malware_ignore_path(path,added_date,resource_type) " "SELECT path,added_date,'file' FROM malware_ignore_path" ) migrator.sql("DROP TABLE malware_ignore_path") migrator.sql( "ALTER TABLE tmp_malware_ignore_path RENAME TO malware_ignore_path" ) migrator.sql( "CREATE INDEX malware_ignore_path_resource_type " "ON malware_ignore_path (resource_type)" ) def rollback(*_, **__): pass defence360agent/migrations/165_add_db_fields_to_malware_history.py0000644000000000000000000000202100000000000022400 0ustar from peewee import CharField, Check from defence360agent.utils import importer MalwareScanResourceType = importer.get( module="imav.malwarelib.config", name="MalwareScanResourceType", default=None, ) def migrate(migrator, *_, fake=False, **__): malware_history = migrator.orm["malware_history"] migrator.add_fields( malware_history, app_name=CharField(null=True), resource_type=CharField( null=False, constraints=[ Check( "resource_type in {}".format( ( MalwareScanResourceType.DB.value, MalwareScanResourceType.FILE.value, ) ) ) ], default=MalwareScanResourceType.FILE.value, ), ) def rollback(migrator, *_, fake=False, **__): malware_history = migrator.orm["malware_history"] migrator.remove_fields(malware_history, "app_name", "resource_type") defence360agent/migrations/166_add_id_field_to_malware_ignore_path.py0000644000000000000000000000172100000000000023031 0ustar from time import time from peewee import CharField, Check, IntegerField, Model, PrimaryKeyField class MalwareIgnorePath(Model): class Meta: db_table = "malware_ignore_path" indexes = ((("path", "resource_type"), True),) # True refers to unique CACHE = None id = PrimaryKeyField() path = CharField() resource_type = CharField( null=False, constraints=[Check("resource_type in ('file','db')")] ) added_date = IntegerField(null=False, default=lambda: int(time())) def migrate(migrator, *_, fake=False, **__): migrator.sql( "ALTER TABLE malware_ignore_path RENAME TO malware_ignore_path_old;" ) migrator.create_model(MalwareIgnorePath) migrator.sql( "INSERT INTO malware_ignore_path(path,added_date,resource_type) " "SELECT path,added_date,resource_type FROM malware_ignore_path_old" ) migrator.sql("DROP TABLE malware_ignore_path_old;") def rollback(*_, **__): pass defence360agent/migrations/167_remote_iplist.py0000644000000000000000000000176600000000000016562 0ustar from peewee import CharField, Model, IntegerField, CompositeKey class IPListRecord(Model): network_address = IntegerField(null=False) netmask = IntegerField(null=False) version = IntegerField(null=False) iplist_id = IntegerField(null=False) class Meta: db_table = "iplistrecord" primary_key = CompositeKey( "network_address", "netmask", "version", "iplist_id" ) class IPListPurpose(Model): purpose = CharField(null=False) iplist_id = IntegerField(null=False) class Meta: db_table = "iplistpurpose" primary_key = CompositeKey("purpose", "iplist_id") def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(IPListRecord) migrator.create_model(IPListPurpose) def rollback(migrator, database, fake=False, **kwargs): IPListRecord = migrator.orm["iplistrecord"] migrator.remove_model(IPListRecord) IPListPurpose = migrator.orm["iplistpurpose"] migrator.remove_model(IPListPurpose) defence360agent/migrations/168_add_icontact_throttle.py0000644000000000000000000000156000000000000020235 0ustar from peewee import CharField, Check, IntegerField, Model from defence360agent.contracts.config import IContactMessageType class IContactThrottle(Model): class Meta: db_table = "icontact_throttle" message_type = CharField( primary_key=True, constraints=[ Check( "message_type in {}".format( ( str(IContactMessageType.MALWARE_FOUND), str(IContactMessageType.SCAN_NOT_SCHEDULED), ) ) ) ], ) timestamp = IntegerField(default=0) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(IContactThrottle) def rollback(migrator, database, fake=False, **kwargs): IContactThrottle = migrator.orm["icontact_throttle"] migrator.remove_model(IContactThrottle) defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.py0000644000000000000000000000117300000000000025536 0ustar import time from defence360agent.contracts.config import IContactMessageType def migrate(migrator, database, fake=False, **kwargs): if fake: return IContactThrotle = migrator.orm["icontact_throttle"] IContactThrotle.create( message_type=IContactMessageType.SCAN_NOT_SCHEDULED, timestamp=time.time() + (7 * 86400), ) def rollback(migrator, database, fake=False, **kwargs): if fake: return IContactThrottle = migrator.orm["icontact_throttle"] IContactThrottle.delete().where( IContactThrottle.message_type == IContactMessageType.SCAN_NOT_SCHEDULED ).execute() defence360agent/migrations/170_add_db_fields_to_malware_history.py0000644000000000000000000000073700000000000022410 0ustar from peewee import CharField def migrate(migrator, *_, fake=False, **__): malware_history = migrator.orm["malware_history"] migrator.add_fields( malware_history, db_host=CharField(null=True), db_port=CharField(null=True), db_name=CharField(null=True), ) def rollback(migrator, *_, fake=False, **__): malware_history = migrator.orm["malware_history"] migrator.remove_fields(malware_history, "db_host", "db_port", "db_name") defence360agent/migrations/180_move_captcha_configs.py0000644000000000000000000000043000000000000020022 0ustar """ No need to rollback captcha keys config because WebshieldCaptchaKeys plugin recreates it on the agent start so just stubbing the migration """ def migrate(migrator, database, fake=False, **kwargs): pass def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/182_remove_constraints_from_icontact_throttle.py0000644000000000000000000000162100000000000024446 0ustar """ Drop constrains for icontact_throttle.message_type, since correlation server can set any type (DEF-19971). """ from peewee import CharField, IntegerField, Model class IContactThrottle(Model): class Meta: db_table = "icontact_throttle" message_type = CharField(primary_key=True) #: The last time we sent a notification about :attr:`message_type` timestamp = IntegerField(default=0) def migrate(migrator, database, fake=False, **kwargs): if fake: return migrator.sql( "ALTER TABLE icontact_throttle RENAME TO icontact_throttle_old" ) migrator.create_model(IContactThrottle) migrator.sql( "INSERT INTO icontact_throttle(message_type,timestamp) " "SELECT message_type,timestamp FROM icontact_throttle_old" ) migrator.sql("DROP TABLE icontact_throttle_old") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/183_add_user_field_to_malware_scans.py0000644000000000000000000000065700000000000022231 0ustar from peewee import CharField def migrate(migrator, *_, fake=False, **__): if fake: return malware_scans = migrator.orm["malware_scans"] migrator.add_fields( malware_scans, initiator=CharField(null=True), ) def rollback(migrator, *_, fake=False, **__): if fake: return malware_scans = migrator.orm["malware_scans"] migrator.remove_fields(malware_scans, "initiator") defence360agent/migrations/184_create_a_table_for_secure_site_permissions.py0000644000000000000000000000071700000000000024502 0ustar import peewee as pw class SecureSite(pw.Model): class Meta: db_table = "secure_site" user = pw.CharField(unique=True) subscription_type = pw.TextField( null=False, constraints=[pw.Check("subscription_type in ('basic','pro')")], default="basic", ) def migrate(migrator, _db, fake=False, **__): if fake: return migrator.create_model(SecureSite) def rollback(*_, **__): """Not supported""" defence360agent/migrations/185_delete_all_secure_site_id.py0000644000000000000000000000101700000000000021030 0ustar import logging from pathlib import Path logger = logging.getLogger(__name__) def migrate(migrator, _db, fake=False, **__): if fake: return try: id_files = Path("/").glob("home*/*/.secure_site_id") for id_file in id_files: if not id_file.is_symlink(): id_file.unlink(missing_ok=True) except Exception: logger.exception( "An exception occurred while deleting .secure_site_id files" ) def rollback(*_, **__): """Not supported""" defence360agent/migrations/186_add_user_field_to_icontact_throttle.py0000644000000000000000000000307600000000000023144 0ustar from peewee import CompositeKey, Model, CharField, IntegerField def migrate(migrator, *_, fake=False, **__): if fake: return icontact_throttle = migrator.orm["icontact_throttle"] class TmpIContactThrottle(Model): class Meta: db_table = "tmp_icontact_throttle" primary_key = CompositeKey("message_type", "user") message_type = CharField() user = CharField(null=True) timestamp = IntegerField(default=0) migrator.add_fields( icontact_throttle, user=CharField(null=True), ) # change the primary key migrator.create_model(TmpIContactThrottle) migrator.sql( "INSERT INTO tmp_icontact_throttle (message_type, user, timestamp) " "SELECT message_type, user, timestamp FROM icontact_throttle" ) migrator.sql("DROP TABLE icontact_throttle") migrator.sql( "ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle", ) def rollback(migrator, *_, fake=False, **__): if fake: return class TmpIContactThrottle(Model): class Meta: db_table = "icontact_throttle" message_type = CharField(primary_key=True) timestamp = IntegerField(default=0) migrator.create_model(TmpIContactThrottle) migrator.sql( "INSERT INTO tmp_icontact_throttle (message_type, timestamp) " "SELECT message_type, timestamp FROM icontact_throttle" ) migrator.sql("DROP TABLE icontact_throttle") migrator.sql( "ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle", ) defence360agent/migrations/187_fix_scan_unserialization.py0000644000000000000000000000301700000000000020766 0ustar """ Used to fix issue with inability to unserialize stored scans. See DEF-23121 for details. """ import importlib import logging import pickle from pathlib import Path logger = logging.getLogger(__name__) SCANS_PATH = Path("/var/imunify360/aibolit/scans.pickle") IM360_MALWARELIB = "im360.malwarelib" AV_MALWARELIB = "imav.malwarelib" class AVUnpickler(pickle.Unpickler): def find_class(self, module, name): try: return super().find_class(module, name) except ModuleNotFoundError: if module.startswith(IM360_MALWARELIB): av_module = importlib.import_module( module.replace(IM360_MALWARELIB, AV_MALWARELIB) ) return getattr(av_module, name) raise def dump(obj, path): temp_path = path.with_name(path.name + ".temp") with temp_path.open("wb") as f: pickle.dump(obj, f) # to avoid the possibility of leaving a broken file, # if any errors occurred above temp_path.replace(path) def migrate(migrator, *_, fake=False, **__): if fake or not SCANS_PATH.exists(): return if IM360_MALWARELIB.encode() in SCANS_PATH.read_bytes(): try: with SCANS_PATH.open("rb") as f: obj = AVUnpickler(f).load() except Exception as exc: logger.exception( "Failed to load pickle scans %s: %s", SCANS_PATH, exc ) else: dump(obj, SCANS_PATH) def rollback(migrator, *_, fake=False, **__): pass defence360agent/migrations/188_add_protection_status_field_myimunify.py0000644000000000000000000000067200000000000023553 0ustar from peewee import BooleanField, CharField, Model class MyImunify(Model): class Meta: db_table = "myimunify" user = CharField(unique=True) protection = BooleanField(null=False, default=False) def migrate(migrator, _db, fake=False, **__): if fake: return migrator.create_model(MyImunify) def rollback(migrator, _db, fake=False, **__): if fake: return migrator.remove_model(MyImunify) defence360agent/migrations/189_add_messages_to_send_nr.py0000644000000000000000000000071300000000000020527 0ustar from peewee import FloatField, Model, BlobField class MessageToSend(Model): class Meta: db_table = "messages_to_send_nr" timestamp = FloatField(null=False) message = BlobField(null=False) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(MessageToSend) def rollback(migrator, database, fake=False, **kwargs): MessageToSend = migrator.orm["messages_to_send"] migrator.drop_model(MessageToSend) defence360agent/migrations/190_add_analyst_cleanup_request_table.py0000644000000000000000000000217000000000000022576 0ustar from peewee import ( Model, AutoField, CharField, TextField, TimestampField, Check, ) from datetime import datetime, timezone class AnalystCleanupRequest(Model): """ Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. """ class Meta: db_table = "analyst_cleanup_requests" id = AutoField() username = CharField(null=False) zendesk_id = CharField(null=False) ticket_link = TextField(null=False) created_at = TimestampField(null=False, default=datetime.now(timezone.utc)) status = CharField( null=False, default="pending", constraints=[Check("status in ('pending','in_progress','completed')")], ) last_updated = TimestampField( null=False, default=datetime.now(timezone.utc) ) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(AnalystCleanupRequest) def rollback(migrator, database, fake=False, **kwargs): analyst_cleanup_request = migrator.orm["analyst_cleanup_requests"] migrator.drop_model(analyst_cleanup_request) defence360agent/migrations/191_create_wordpress_incident_table.py0000644000000000000000000000246600000000000022275 0ustar """Create wordpress_incident table for WordPress CVE protection incidents. This migration creates a dedicated table for WordPress incidents rather than using the generic incident table. This allows for better separation of concerns and cleaner data model. """ import peewee as pw from playhouse.sqlite_ext import JSONField class WordpressIncident(pw.Model): id = pw.IntegerField(primary_key=True, null=True) plugin = pw.CharField(null=True) rule = pw.CharField(null=True) timestamp = pw.FloatField(null=True) retries = pw.IntegerField(null=True) severity = pw.IntegerField(null=True) name = pw.CharField(null=True) description = pw.TextField(null=True) abuser = pw.CharField(null=True) country = pw.CharField(null=True, column_name="country_id") domain = pw.TextField(null=True, default=None) extra_info = JSONField(null=True) sent_to_server = pw.BooleanField(null=False, default=False) class Meta: db_table = "wordpress_incident" def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(WordpressIncident) # Add index on timestamp for performance migrator.add_index(WordpressIncident, "timestamp", unique=False) def rollback(migrator, database, fake=False, **kwargs): migrator.remove_model(WordpressIncident, cascade=True) defence360agent/migrations/192_add_wordpress_incident_unique_index.py0000644000000000000000000000212300000000000023157 0ustar """Add unique composite index to wordpress_incident table for deduplication. This migration adds a unique index on the fields used to identify duplicate incidents (abuser, name, plugin, rule, severity, domain), similar to the aggregation key used in the resident agent's aggregate plugin. """ def migrate(migrator, database, fake=False, **kwargs): """Add unique composite index for incident deduplication.""" WordpressIncident = migrator.orm["wordpress_incident"] # Create unique index on the aggregate key fields # This allows ON CONFLICT handling for incident deduplication migrator.add_index( WordpressIncident, "abuser", "name", "plugin", "rule", "severity", "domain", unique=True, ) def rollback(migrator, database, fake=False, **kwargs): """Remove the unique composite index.""" WordpressIncident = migrator.orm["wordpress_incident"] migrator.drop_index( WordpressIncident, "abuser", "name", "plugin", "rule", "severity", "domain", ) defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.py0000644000000000000000000000111500000000000025314 0ustar """Remove sent_to_server column from wordpress_incident table. The sent_to_server field is no longer needed for WordPress incident tracking. """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] migrator.remove_fields(WordpressIncident, "sent_to_server") def rollback(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] migrator.add_fields( WordpressIncident, sent_to_server=pw.BooleanField(null=False, default=False), ) defence360agent/migrations/194_add_wp_disabled_rules.py0000644000000000000000000000167400000000000020200 0ustar """Add wp_disabled_rules table for WordPress-specific disabled rules. This table stores disabled WordPress protection rules with a scope-based design supporting global and domain-level disables. """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): class WPDisabledRule(pw.Model): class Meta: db_table = "wp_disabled_rules" indexes = ((("rule_id", "scope", "scope_value"), True),) id = pw.PrimaryKeyField() rule_id = pw.CharField(null=False) scope = pw.CharField(null=False) scope_value = pw.CharField(null=True) disabled_at = pw.FloatField(null=False) source = pw.CharField(null=False) created_by_user_id = pw.IntegerField(null=False) migrator.create_model(WPDisabledRule) def rollback(migrator, database, fake=False, **kwargs): WPDisabledRule = migrator.orm["wp_disabled_rules"] migrator.remove_model(WPDisabledRule) defence360agent/migrations/194_create_nonprivileged_config.py0000644000000000000000000000143400000000000021410 0ustar """ Create imunify360-merged-nonprivileged.config with settings needed by non-root processes. """ import logging from defence360agent.contracts.config import Merger logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: # Trigger a full config merge which will: # - Write nonprivileged settings to imunify360-merged-nonprivileged.config # - Write all settings to imunify360-merged.config Merger.update_merged_config() logger.info("Successfully created nonprivileged config") except Exception as exc: logger.error( "Failed to create nonprivileged config: %s", exc, ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/195_create_wordpress_site.py0000644000000000000000000000115500000000000020273 0ustar """Create wordpress_site table. migrator.create_model() emits CREATE TABLE IF NOT EXISTS, so this is a no-op on installs where imav/014 (now retained as a no-op) had already created the table. """ from peewee import IntegerField, CharField, Model class WordpressSite(Model): class Meta: db_table = "wordpress_site" docroot = CharField(primary_key=True, null=False) domain = CharField(null=False) uid = IntegerField(null=False) def migrate(migrator, database, fake=False, **kwargs): migrator.create_model(WordpressSite) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/196_add_disabled_rules_sync_ts.py0000644000000000000000000000106700000000000021232 0ustar """Add disabled_rules_sync_ts field to wordpress_site table. Tracks when disabled-rules.php was last written for each site. """ from peewee import FloatField def migrate(migrator, database, fake=False, **kwargs): WordpressSite = migrator.orm["wordpress_site"] migrator.add_fields( WordpressSite, disabled_rules_sync_ts=FloatField(null=True, default=None), ) def rollback(migrator, database, fake=False, **kwargs): WordpressSite = migrator.orm["wordpress_site"] migrator.remove_fields(WordpressSite, "disabled_rules_sync_ts") defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py0000644000000000000000000000124500000000000023636 0ustar """Add manually_deleted_at column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/015 (now retained as a no-op) had already added the column. """ from peewee import TimestampField def migrate(migrator, database, fake=False, **kwargs): if fake: return columns = [col.name for col in database.get_columns("wordpress_site")] if "manually_deleted_at" not in columns: WordpressSite = migrator.orm["wordpress_site"] migrator.add_columns( WordpressSite, manually_deleted_at=TimestampField(null=True) ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/198_add_wordpress_site_version.py0000644000000000000000000000121100000000000021321 0ustar """Add version column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/017 (now retained as a no-op) had already added the column. """ from peewee import CharField def migrate(migrator, database, fake=False, **kwargs): if fake: return columns = [col.name for col in database.get_columns("wordpress_site")] if "version" not in columns: WordpressSite = migrator.orm["wordpress_site"] migrator.add_columns( WordpressSite, version=CharField(default="1.0.0", null=False) ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/199_proactive_log_permission.py0000644000000000000000000000275400000000000021013 0ustar """Allow `log` as a proactive feature-management permission value. Relaxes the CHECK constraint on ``feature_management_permissions.proactive`` from ``(NA, FULL)`` to ``(NA, LOG, FULL)``. SQLite cannot alter CHECK constraints in place, so the table is recreated. DEF-42523. """ from peewee import CharField, Check, Model, TextField from defence360agent.feature_management.constants import ( AV_REPORT, FULL, LOG, NA, ) class FeatureManagementPerms(Model): class Meta: db_table = "feature_management_permissions" user = CharField(unique=True) proactive = TextField( null=False, constraints=[ Check("proactive in ('{}','{}','{}')".format(NA, LOG, FULL)) ], default=FULL, ) av = TextField( null=False, constraints=[ Check("av in ('{}','{}','{}')".format(NA, AV_REPORT, FULL)) ], default=AV_REPORT, ) def migrate(migrator, database, fake=False, **kwargs): if fake: return migrator.sql( "ALTER TABLE feature_management_permissions " "RENAME TO feature_management_permissions_old" ) migrator.create_model(FeatureManagementPerms) migrator.sql( "INSERT INTO feature_management_permissions(user, proactive, av) " "SELECT user, proactive, av FROM feature_management_permissions_old" ) migrator.sql("DROP TABLE feature_management_permissions_old") def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/200_seed_per_user_waf_enabled.py0000644000000000000000000000414700000000000021016 0ustar import asyncio import logging from defence360agent.contracts.config import ( UserConfig, UserType, choose_value_from_config, ) from defence360agent.utils import importer panel_users = importer.get( module="imav.malwarelib.utils.user_list", name="panel_users", default=None, ) logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake or panel_users is None: return loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) try: try: users = loop.run_until_complete(panel_users()) except Exception: logger.exception( "Failed to enumerate panel users for waf_enabled seed" ) return for entry in users: try: username = entry["user"] except (KeyError, TypeError) as e: logger.warning( "Skipping malformed panel entry %r during waf_enabled" " seed: %s", entry, e, ) continue try: _, source = choose_value_from_config( "WORDPRESS", "waf_enabled", username=username, ) if source != UserType.ROOT: continue except Exception as e: logger.warning( "Failed to read waf_enabled for user %s while seeding: %s", username, e, ) continue try: UserConfig(username=username).dict_to_config( {"WORDPRESS": {"waf_enabled": True}}, without_defaults=True, ) except Exception as e: logger.warning( "Failed to seed WORDPRESS.waf_enabled for user %s: %s", username, e, ) finally: loop.close() def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/201_rerender_nonprivileged_config.py0000644000000000000000000000110200000000000021730 0ustar """ Re-render imunify360-merged-nonprivileged.config so the newly-split MALWARE_SCANNING.enable_scan_modsec key lands on upgrade. """ import logging from defence360agent.contracts.config import Merger logger = logging.getLogger(__name__) def migrate(migrator, database, fake=False, **kwargs): if fake: return try: Merger.update_merged_config() except Exception as exc: logger.error( "Failed to re-render nonprivileged config: %s", exc, ) def rollback(migrator, database, fake=False, **kwargs): pass defence360agent/migrations/202_add_wordpress_incident_bucket.py0000644000000000000000000000413200000000000021731 0ustar """Aggregate WordPress incidents per minute instead of forever.""" import peewee as pw OLD_UNIQUE_KEY = ("abuser", "name", "plugin", "rule", "severity", "domain") NEW_UNIQUE_KEY = OLD_UNIQUE_KEY + ("bucket",) def migrate(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] migrator.add_fields(WordpressIncident, bucket=pw.IntegerField(null=True)) # A NULL bucket is distinct from every other NULL, which would opt # existing rows out of deduplication entirely. migrator.sql( "UPDATE wordpress_incident SET bucket = CAST(timestamp / 60 AS" " INTEGER) WHERE bucket IS NULL AND timestamp IS NOT NULL" ) migrator.drop_index(WordpressIncident, *OLD_UNIQUE_KEY) migrator.add_index(WordpressIncident, *NEW_UNIQUE_KEY, unique=True) # Duplicate of wordpress_incident_timestamp, left behind by migration 191. migrator.sql("DROP INDEX IF EXISTS wordpressincident_timestamp") def rollback(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] key = ", ".join(OLD_UNIQUE_KEY) same_key = " AND ".join( f"dup.{column} IS wordpress_incident.{column}" for column in OLD_UNIQUE_KEY ) migrator.sql( "CREATE INDEX IF NOT EXISTS wordpressincident_timestamp" " ON wordpress_incident (timestamp)" ) migrator.drop_index(WordpressIncident, *NEW_UNIQUE_KEY) # Windows of the same attack are separate rows now, which the old index # forbids. Fold them back into one before it is restored. migrator.sql( "UPDATE wordpress_incident SET" " retries = (SELECT SUM(dup.retries) FROM wordpress_incident dup" f" WHERE {same_key})," " timestamp = (SELECT MIN(dup.timestamp) FROM wordpress_incident dup" f" WHERE {same_key})" ) migrator.sql( "DELETE FROM wordpress_incident WHERE id NOT IN" f" (SELECT MIN(id) FROM wordpress_incident GROUP BY {key})" ) migrator.remove_fields(WordpressIncident, "bucket") migrator.add_index(WordpressIncident, *OLD_UNIQUE_KEY, unique=True) defence360agent/migrations/203_add_wordpress_incident_unsent_retries.py0000644000000000000000000000251400000000000023530 0ustar """Track how many occurrences of each wordpress_incident correlation owes. The counter is decremented only once the transport acknowledges the message that carried them, so the periodic task can re-send incidents whose message was lost. A counter rather than a flag: occurrences merged into a row that was already reported still have to reach correlation. Rows that already exist when the column is added take the DEFAULT of 0 and are therefore treated as fully reported. Their delivery was never tracked, and re-sending a whole retention window of history on upgrade would be worse than leaving them alone. """ import peewee as pw def migrate(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] migrator.add_fields( WordpressIncident, # the DEFAULT belongs in the schema, not just in peewee: # src/rpm-tests/test_wordpress/test_list_incidents.py inserts rows # with raw SQL that names its columns explicitly unsent_retries=pw.IntegerField( null=False, default=0, index=True, constraints=[pw.SQL("DEFAULT 0")], ), ) def rollback(migrator, database, fake=False, **kwargs): WordpressIncident = migrator.orm["wordpress_incident"] migrator.remove_fields(WordpressIncident, "unsent_retries") defence360agent/migrations/__init__.py0000644000000000000000000000000000000000000015021 0ustar defence360agent/migrations/__pycache__/0000755000000000000000000000000000000000000015132 5ustar defence360agent/migrations/__pycache__/001_initial.cpython-311.opt-1.pyc0000644000000000000000000001144200000000000022606 0ustar r_jdZddlZGddejZGddejZGddejZGd d ejZdd Zdd Z dS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Nc\eZdZejddZejdZejdZej dZ ejdZ ejdZ ejdZ ejdZejdZGddZdS)IncidentT primary_keynullrceZdZdZdS) Incident.MetaincidentN__name__ __module__ __qualname__db_table[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/001_initial.pyMetar #srrN)r rrpw IntegerFieldid CharFieldpluginrule FloatField timestampretriesseverityname descriptionabuserrrrrrrs T 5 5 5B R\t $ $ $F 2sR $T:::I##########rr3Fc |t|t|t|tdS)z%In memory of former create_db() (RIP)N) create_modelrr#r.r3migratordatabasefakekwargss rmigrater>Es[ (### &!!! *+++ ,'''''rc dS)zNothing to rollback.Nrr9s rrollbackr@Nsr)F) __doc__peeweerModelrr#r.r3r>r@rrrrDs(     rx        RX   '''''rx'''#####28###((((rdefence360agent/migrations/__pycache__/001_initial.cpython-311.pyc0000644000000000000000000001144200000000000021647 0ustar r_jdZddlZGddejZGddejZGddejZGd d ejZdd Zdd Z dS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Nc\eZdZejddZejdZejdZej dZ ejdZ ejdZ ejdZ ejdZejdZGddZdS)IncidentT primary_keynullrceZdZdZdS) Incident.MetaincidentN__name__ __module__ __qualname__db_table[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/001_initial.pyMetar #srrN)r rrpw IntegerFieldid CharFieldpluginrule FloatField timestampretriesseverityname descriptionabuserrrrrrrs T 5 5 5B R\t $ $ $F 2sR $T:::I##########rr3Fc |t|t|t|tdS)z%In memory of former create_db() (RIP)N) create_modelrr#r.r3migratordatabasefakekwargss rmigrater>Es[ (### &!!! *+++ ,'''''rc dS)zNothing to rollback.Nrr9s rrollbackr@Nsr)F) __doc__peeweerModelrr#r.r3r>r@rrrrDs(     rx        RX   '''''rx'''#####28###((((rdefence360agent/migrations/__pycache__/002_infected_domain_list.cpython-311.opt-1.pyc0000644000000000000000000000435200000000000025323 0ustar r_jHdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZejdZejdZejdZej Z GddZ dS)InfectedDomainListT) primary_keyF)nullceZdZdZdS)InfectedDomainList.Metainfected_domain_listN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/002_infected_domain_list.pyMetars)rrN) r r r pw IntegerFieldid CharFieldname threat_type FloatField timestamprrrrrrs T * * *B 2r*s{*********..........rdefence360agent/migrations/__pycache__/002_infected_domain_list.cpython-311.pyc0000644000000000000000000000435200000000000024364 0ustar r_jHdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZejdZejdZejdZej Z GddZ dS)InfectedDomainListT) primary_keyF)nullceZdZdZdS)InfectedDomainList.Metainfected_domain_listN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/002_infected_domain_list.pyMetars)rrN) r r r pw IntegerFieldid CharFieldname threat_type FloatField timestamprrrrrrs T * * *B 2r*s{*********..........rdefence360agent/migrations/__pycache__/003_import_from_list.cpython-311.opt-1.pyc0000644000000000000000000000410600000000000024546 0ustar r_j'6dZddlZddlZddlmZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) N) IntegerFieldFc |jd}||tjdt dddS)zWrite your migrations here.iplistT)nullcBttjS)N)inttimed/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/003_import_from_list.pyzmigrate..#sc$)++6F6Fr )rdefault) imported_fromctimeN)orm add_fieldspw CharFieldrmigratordatabasefakekwargsIPLists r migraters^\( #F l---.F.FGGGr c N|jd}||dddS)z$Write your rollback migrations here.rrcreatedN)r remove_fieldsrs r rollbackr's.\( #F 6?I>>>>>r )F)__doc__r peeweerrrrr r r r"sj(     ??????r defence360agent/migrations/__pycache__/003_import_from_list.cpython-311.pyc0000644000000000000000000000410600000000000023607 0ustar r_j'6dZddlZddlZddlmZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) N) IntegerFieldFc |jd}||tjdt dddS)zWrite your migrations here.iplistT)nullcBttjS)N)inttimed/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/003_import_from_list.pyzmigrate..#sc$)++6F6Fr )rdefault) imported_fromctimeN)orm add_fieldspw CharFieldrmigratordatabasefakekwargsIPLists r migraters^\( #F l---.F.FGGGr c N|jd}||dddS)z$Write your rollback migrations here.rrcreatedN)r remove_fieldsrs r rollbackr's.\( #F 6?I>>>>>r )F)__doc__r peeweerrrrr r r r"sj(     ??????r ././@LongLink0000644000000000000000000000014600000000000007774 Lustar defence360agent/migrations/__pycache__/004_add_username_to_infected_domain_list.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/004_add_username_to_infected_domain_list.cpython-311.opt-1.py0000644000000000000000000000327200000000000030373 0ustar r_j{"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)Ninfected_domain_listT)null)username)orm add_fieldspw CharFieldmigratordatabasefakekwargsInfectedDomainLists x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/004_add_username_to_infected_domain_list.pymigraters=!&<= *R\t5L5L5LMMMMMc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs-!&<= -z:::::r)F)__doc__peeweer rrrrrsS*NNNN ;;;;;;rdefence360agent/migrations/__pycache__/004_add_username_to_infected_domain_list.cpython-311.pyc0000644000000000000000000000327200000000000027577 0ustar r_j{"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)Ninfected_domain_listT)null)username)orm add_fieldspw CharFieldmigratordatabasefakekwargsInfectedDomainLists x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/004_add_username_to_infected_domain_list.pymigraters=!&<= *R\t5L5L5LMMMMMc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs-!&<= -z:::::r)F)__doc__peeweer rrrrrsS*NNNN ;;;;;;rdefence360agent/migrations/__pycache__/005_timeout_in_iplist.cpython-311.opt-1.pyc0000644000000000000000000000331100000000000024715 0ustar r_j"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)zWrite your migrations here.iplistT)null)deepN)orm add_fieldspw IntegerFieldmigratordatabasefakekwargsIPLists e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/005_timeout_in_iplist.pymigraters: \( #F R_$%?%?%?@@@@@c L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackr s* \( #F 66*****r)F)__doc__peeweer rrrrrsS*AAAA++++++rdefence360agent/migrations/__pycache__/005_timeout_in_iplist.cpython-311.pyc0000644000000000000000000000331100000000000023756 0ustar r_j"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)zWrite your migrations here.iplistT)null)deepN)orm add_fieldspw IntegerFieldmigratordatabasefakekwargsIPLists e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/005_timeout_in_iplist.pymigraters: \( #F R_$%?%?%?@@@@@c L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackr s* \( #F 66*****r)F)__doc__peeweer rrrrrsS*AAAA++++++rdefence360agent/migrations/__pycache__/006_comment_in_plist.cpython-311.opt-1.pyc0000644000000000000000000000331000000000000024520 0ustar r_j"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)zWrite your migrations here.iplistT)null)commentN)orm add_fieldspw CharFieldmigratordatabasefakekwargsIPLists d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/006_comment_in_plist.pymigraters: \( #F  $(?(?(?@@@@@c L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackr s* \( #F 69-----r)F)__doc__peeweer rrrrrsS*AAAA......rdefence360agent/migrations/__pycache__/006_comment_in_plist.cpython-311.pyc0000644000000000000000000000331000000000000023561 0ustar r_j"dZddlZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NFc t|jd}||tjddS)zWrite your migrations here.iplistT)null)commentN)orm add_fieldspw CharFieldmigratordatabasefakekwargsIPLists d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/006_comment_in_plist.pymigraters: \( #F  $(?(?(?@@@@@c L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackr s* \( #F 69-----r)F)__doc__peeweer rrrrrsS*AAAA......rdefence360agent/migrations/__pycache__/007_add_country_code_fields.cpython-311.opt-1.pyc0000644000000000000000000000555700000000000026030 0ustar r_jjHdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NcpeZdZejdddZejdZGddZdS) CountryTF) max_length primary_keynullrceZdZdZdS) Country.MetacountryN)__name__ __module__ __qualname__db_tablek/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/007_add_country_code_fields.pyMetar srrN)r rrpw CharFieldcodenamerrrrrrsh 2<1$U C C CD 2r/s{*bh    ######rdefence360agent/migrations/__pycache__/007_add_country_code_fields.cpython-311.pyc0000644000000000000000000000555700000000000025071 0ustar r_jjHdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NcpeZdZejdddZejdZGddZdS) CountryTF) max_length primary_keynullrceZdZdZdS) Country.MetacountryN)__name__ __module__ __qualname__db_tablek/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/007_add_country_code_fields.pyMetar srrN)r rrpw CharFieldcodenamerrrrrrsh 2<1$U C C CD 2r/s{*bh    ######rdefence360agent/migrations/__pycache__/008_fill_countries.cpython-311.opt-1.pyc0000644000000000000000000000105200000000000024201 0ustar r_jddZddZdS)Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/008_fill_countries.pymigrater Dc dS)z$Write your rollback migrations here.Nrrs r rollbackr r r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/008_fill_countries.cpython-311.pyc0000644000000000000000000000105200000000000023242 0ustar r_jddZddZdS)Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/008_fill_countries.pymigrater Dc dS)z$Write your rollback migrations here.Nrrs r rollbackr r r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/009_drop_blocklist_history.cpython-311.opt-1.pyc0000644000000000000000000000470500000000000025764 0ustar r_j}HdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZejddZejdZejdZej dZ ejdZ GddZ dS)BlocklistHistoryT) primary_keynull)rceZdZdZdS)BlocklistHistory.Metablocklist_historyN)__name__ __module__ __qualname__db_tablej/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/009_drop_blocklist_history.pyMetars&rrN) r r r pw IntegerFieldid CharFieldpluginrule FloatField timestampiprrrrrrs T 5 5 5B R\t $ $ $F 2r)s{('''''rx''',,,, ,,,,,,rdefence360agent/migrations/__pycache__/009_drop_blocklist_history.cpython-311.pyc0000644000000000000000000000470500000000000025025 0ustar r_j}HdZddlZGddejZddZddZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZejddZejdZejdZej dZ ejdZ GddZ dS)BlocklistHistoryT) primary_keynull)rceZdZdZdS)BlocklistHistory.Metablocklist_historyN)__name__ __module__ __qualname__db_tablej/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/009_drop_blocklist_history.pyMetars&rrN) r r r pw IntegerFieldid CharFieldpluginrule FloatField timestampiprrrrrrs T 5 5 5B R\t $ $ $F 2r)s{('''''rx''',,,, ,,,,,,rdefence360agent/migrations/__pycache__/010_drop_country_entities.cpython-311.opt-1.pyc0000644000000000000000000000355100000000000025612 0ustar r_j dZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc .|jd}|jd}|jd}||d||d||d||d||dS)zWrite your migrations here.iplistincidentcountryN)orm drop_index remove_fields remove_model)migratordatabasefakekwargsIPListIncidentCountrys i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/010_drop_country_entities.pymigraters\( #F|J'Hl9%G  *** ),,, 69--- 8Y/// '"""""c dS)z$Write your rollback migrations here.N)r r r r s rrollbackr&sDrN)F)__doc__rrrrrrsA, # # # #      rdefence360agent/migrations/__pycache__/010_drop_country_entities.cpython-311.pyc0000644000000000000000000000355100000000000024653 0ustar r_j dZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc .|jd}|jd}|jd}||d||d||d||d||dS)zWrite your migrations here.iplistincidentcountryN)orm drop_index remove_fields remove_model)migratordatabasefakekwargsIPListIncidentCountrys i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/010_drop_country_entities.pymigraters\( #F|J'Hl9%G  *** ),,, 69--- 8Y/// '"""""c dS)z$Write your rollback migrations here.N)r r r r s rrollbackr&sDrN)F)__doc__rrrrrrsA, # # # #      rdefence360agent/migrations/__pycache__/011_create_new_country_entities.cpython-311.opt-1.pyc0000644000000000000000000001216600000000000026765 0ustar r_j dZddlmZddlZGddejZGddejZGdd ejZd d Zd d Z dS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) )timeNceZdZejddZejdddZejdZGddZd S) CountryTF primary_keynull) max_lengthuniquerrceZdZdZdS) Country.MetacountryN__name__ __module__ __qualname__db_tableo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/011_create_new_country_entities.pyMetar srrN) rrrpw CharFieldidcodenamerrrrrrs~ $U 3 3 3B 2<1T > > >D 2zCountryList.:ss466{{r)rdefaultr ceZdZdZdS)CountryList.Meta country_listNrrrrrr0>s!rrN)rrrr(r)IP_LISTSrr$rrrChecklistname IntegerFieldctimecommentrrrrr'r'.s E Eu~H b dGGGGr| *I!J!J KH BO/B/B C C CEbl%%%G""""""""""rr'Fc |jd}|jd}|t||t jtd||t jtd|t |tdS)zWrite your migrations here.iplistincidentTr )rN)orm create_modelr add_fieldsrr$rr')migratordatabasefakekwargsIPListIncidents rmigraterDBs\( #F|J'H '""" (:7(N(N(NOOO ",W4@@@ .))) +&&&&&rc ^|jd}|jd}|jd}|jd}|jd}||d||d||||||dS)z$Write your rollback migrations here.rr#r1r9r:N)r; remove_fields remove_model) r>r?r@rArrr'rBrCs rrollbackrHTsl9%G\"34N,~.K \( #F|J'H 69--- 8Y/// .))) +&&& '"""""r)F) __doc__rpeeweerModelrrr'rDrHrrrrLs*bh%%%%%RX%%%""""""("""(''''$ # # # # # #rdefence360agent/migrations/__pycache__/011_create_new_country_entities.cpython-311.pyc0000644000000000000000000001216600000000000026026 0ustar r_j dZddlmZddlZGddejZGddejZGdd ejZd d Zd d Z dS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) )timeNceZdZejddZejdddZejdZGddZd S) CountryTF primary_keynull) max_lengthuniquerrceZdZdZdS) Country.MetacountryN__name__ __module__ __qualname__db_tableo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/011_create_new_country_entities.pyMetar srrN) rrrpw CharFieldidcodenamerrrrrrs~ $U 3 3 3B 2<1T > > >D 2zCountryList.:ss466{{r)rdefaultr ceZdZdZdS)CountryList.Meta country_listNrrrrrr0>s!rrN)rrrr(r)IP_LISTSrr$rrrChecklistname IntegerFieldctimecommentrrrrr'r'.s E Eu~H b dGGGGr| *I!J!J KH BO/B/B C C CEbl%%%G""""""""""rr'Fc |jd}|jd}|t||t jtd||t jtd|t |tdS)zWrite your migrations here.iplistincidentTr )rN)orm create_modelr add_fieldsrr$rr')migratordatabasefakekwargsIPListIncidents rmigraterDBs\( #F|J'H '""" (:7(N(N(NOOO ",W4@@@ .))) +&&&&&rc ^|jd}|jd}|jd}|jd}|jd}||d||d||||||dS)z$Write your rollback migrations here.rr#r1r9r:N)r; remove_fields remove_model) r>r?r@rArrr'rBrCs rrollbackrHTsl9%G\"34N,~.K \( #F|J'H 69--- 8Y/// .))) +&&& '"""""r)F) __doc__rpeeweerModelrrr'rDrHrrrrLs*bh%%%%%RX%%%""""""("""(''''$ # # # # # #rdefence360agent/migrations/__pycache__/012_fill_countries_and_subnets.cpython-311.opt-1.pyc0000644000000000000000000000117600000000000026570 0ustar r_jdZddZddZdS)zpPeewee migrations: :: UPD: migration not needed anymore, countries and subnets are loaded after files update. Fc dSNmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/012_fill_countries_and_subnets.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/012_fill_countries_and_subnets.cpython-311.pyc0000644000000000000000000000117600000000000025631 0ustar r_jdZddZddZdS)zpPeewee migrations: :: UPD: migration not needed anymore, countries and subnets are loaded after files update. Fc dSNmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/012_fill_countries_and_subnets.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/013_add_indexes_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000311400000000000025512 0ustar r_jSdZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc L|jd}||ddS)zWrite your migrations here.iplistlistnameN)orm add_indexmigratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/013_add_indexes_to_iplist.pymigraters* \( #F vz*****c L|jd}||ddS)z$Write your rollback migrations here.rrN)r drop_indexrs r rollbackrs* \( #F  +++++rN)F)__doc__rrrr rsA,++++ ,,,,,,rdefence360agent/migrations/__pycache__/013_add_indexes_to_iplist.cpython-311.pyc0000644000000000000000000000311400000000000024553 0ustar r_jSdZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc L|jd}||ddS)zWrite your migrations here.iplistlistnameN)orm add_indexmigratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/013_add_indexes_to_iplist.pymigraters* \( #F vz*****c L|jd}||ddS)z$Write your rollback migrations here.rrN)r drop_indexrs r rollbackrs* \( #F  +++++rN)F)__doc__rrrr rsA,++++ ,,,,,,rdefence360agent/migrations/__pycache__/014_add_malware_hits.cpython-311.opt-1.pyc0000644000000000000000000001077700000000000024462 0ustar r_jG dZddlZGddejZGddejZGddejZd d Zd d ZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZGddZejdZejdZejdZ ejdej dgZ ejdZ ejdd Z d S) MalwareScanceZdZdZdS)MalwareScan.Meta malware_scansN__name__ __module__ __qualname__db_tabled/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/014_add_malware_hits.pyMetars"rrT primary_keyFnullz!type in ('on-demand', 'realtime'))r constraintsrrdefaultN)r r r rpw CharFieldscanid IntegerFieldstarted completedChecktypepath total_filesr rrrrs########R\d + + +Fbo5)))GU+++I 2< *M!N!N O   D 2Bs,/Kn-J %:;!&<=  ###  $$$ )*** *+++++r)F) __doc__peeweerModelrr#r.r8r>r rrrBs* 9 9 9 9 9"( 9 9 9 : : : : : : : :********---- , , , , , ,rdefence360agent/migrations/__pycache__/014_add_malware_hits.cpython-311.pyc0000644000000000000000000001077700000000000023523 0ustar r_jG dZddlZGddejZGddejZGddejZd d Zd d ZdS) aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) NceZdZGddZejdZejdZejdZ ejdej dgZ ejdZ ejdd Z d S) MalwareScanceZdZdZdS)MalwareScan.Meta malware_scansN__name__ __module__ __qualname__db_tabled/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/014_add_malware_hits.pyMetars"rrT primary_keyFnullz!type in ('on-demand', 'realtime'))r constraintsrrdefaultN)r r r rpw CharFieldscanid IntegerFieldstarted completedChecktypepath total_filesr rrrrs########R\d + + +Fbo5)))GU+++I 2< *M!N!N O   D 2Bs,/Kn-J %:;!&<=  ###  $$$ )*** *+++++r)F) __doc__peeweerModelrr#r.r8r>r rrrBs* 9 9 9 9 9"( 9 9 9 : : : : : : : :********---- , , , , , ,rdefence360agent/migrations/__pycache__/015_add_iplist_expiration_index.cpython-311.opt-1.pyc0000644000000000000000000000152300000000000026726 0ustar r_jiddZddZdS)Fc L|jd}||ddS)zWrite your migrations here.iplist expirationN)orm add_indexmigratordatabasefakekwargsIPLists o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/015_add_iplist_expiration_index.pymigraters* \( #F v|,,,,,c L|jd}||ddS)z$Write your rollback migrations here.rrN)r drop_indexrs r rollbackrs* \( #F  -----rN)F)rrrr rs7---- ......rdefence360agent/migrations/__pycache__/015_add_iplist_expiration_index.cpython-311.pyc0000644000000000000000000000152300000000000025767 0ustar r_jiddZddZdS)Fc L|jd}||ddS)zWrite your migrations here.iplist expirationN)orm add_indexmigratordatabasefakekwargsIPLists o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/015_add_iplist_expiration_index.pymigraters* \( #F v|,,,,,c L|jd}||ddS)z$Write your rollback migrations here.rrN)r drop_indexrs r rollbackrs* \( #F  -----rN)F)rrrr rs7---- ......rdefence360agent/migrations/__pycache__/016_fix_autowhitelist_expiration.cpython-311.opt-1.pyc0000644000000000000000000000200300000000000027171 0ustar r_j&ddlmZdZddZddZdS))timei7AFc |jd}|t|jdk|jt z tkzdS)Niplist) expirationWHITE)ormupdate _MAX_TIMEOUTwherelistnamerrexecute)migratordatabasefakekwargs IPListModels p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/016_fix_autowhitelist_expiration.pymigratersg,x(K,//55   (  !DFF *\ 9 ;giiiiic dS)N)rrrrs rrollbackrsDrN)F)rr rrrrrrsN       rdefence360agent/migrations/__pycache__/016_fix_autowhitelist_expiration.cpython-311.pyc0000644000000000000000000000200300000000000026232 0ustar r_j&ddlmZdZddZddZdS))timei7AFc |jd}|t|jdk|jt z tkzdS)Niplist) expirationWHITE)ormupdate _MAX_TIMEOUTwherelistnamerrexecute)migratordatabasefakekwargs IPListModels p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/016_fix_autowhitelist_expiration.pymigratersg,x(K,//55   (  !DFF *\ 9 ;giiiiic dS)N)rrrrs rrollbackrsDrN)F)rr rrrrrrsN       rdefence360agent/migrations/__pycache__/017_remove_sensor_prefix.cpython-311.opt-1.pyc0000644000000000000000000000134100000000000025424 0ustar r_jedZddZddZdS)z Migrate server-id w/ prefix inside imunify360.id to server-id w/o prefix into i360.id file UPD: migration not needed yet, as far as, the majority of the servers already converted their server-id to w/0 prefix form. Fc dSNmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/017_remove_sensor_prefix.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/017_remove_sensor_prefix.cpython-311.pyc0000644000000000000000000000134100000000000024465 0ustar r_jedZddZddZdS)z Migrate server-id w/ prefix inside imunify360.id to server-id w/o prefix into i360.id file UPD: migration not needed yet, as far as, the majority of the servers already converted their server-id to w/0 prefix form. Fc dSNmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/017_remove_sensor_prefix.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/018_license_info.cpython-311.opt-1.pyc0000644000000000000000000000316700000000000023627 0ustar r_jHDddlZGddejZddZddZdS)NceZdZGddZejdZejddZejdZ ej dZ d S) LicenseceZdZdZdS) License.MetalicenseN)__name__ __module__ __qualname__db_table`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/018_license_info.pyMetarsr rT) primary_keyFr)nulldefault)rN) rr r rpw BooleanFieldstatus IntegerField expirationlimit CharField redirect_urlr r rrrsR_ . . .F eQ777J BO & & &E2r(so+++++bh+++#### !!!!!!r defence360agent/migrations/__pycache__/018_license_info.cpython-311.pyc0000644000000000000000000000316700000000000022670 0ustar r_jHDddlZGddejZddZddZdS)NceZdZGddZejdZejddZejdZ ej dZ d S) LicenseceZdZdZdS) License.MetalicenseN)__name__ __module__ __qualname__db_table`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/018_license_info.pyMetarsr rT) primary_keyFr)nulldefault)rN) rr r rpw BooleanFieldstatus IntegerField expirationlimit CharField redirect_urlr r rrrsR_ . . .F eQ777J BO & & &E2r(so+++++bh+++#### !!!!!!r defence360agent/migrations/__pycache__/019_purge_old_configs.cpython-311.opt-1.pyc0000644000000000000000000000124300000000000024654 0ustar r_j-dZddZddZdS)z^ Purge old configs from config file to prevent of "Unknown field" errors. UPD: Not actual yet Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/019_purge_old_configs.pymigrater c dS)z$Write your rollback migrations here.Nrrs r rollbackr r r N)F)__doc__r rrr r rsA &&&&//////r defence360agent/migrations/__pycache__/019_purge_old_configs.cpython-311.pyc0000644000000000000000000000124300000000000023715 0ustar r_j-dZddZddZdS)z^ Purge old configs from config file to prevent of "Unknown field" errors. UPD: Not actual yet Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/019_purge_old_configs.pymigrater c dS)z$Write your rollback migrations here.Nrrs r rollbackr r r N)F)__doc__r rrr r rsA &&&&//////r defence360agent/migrations/__pycache__/020_malware_scan_types.cpython-311.opt-1.pyc0000644000000000000000000000247200000000000025041 0ustar r_jdZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/020_malware_scan_types.pymigrater s  Dc dS)z$Write your rollback migrations here.Nrrs r rollbackr s  Dr N)F)__doc__r r rr r rsA,          r defence360agent/migrations/__pycache__/020_malware_scan_types.cpython-311.pyc0000644000000000000000000000247200000000000024102 0ustar r_jdZddZddZdS)aPeewee migrations: :: > Model = migrator.orm['name'] > migrator.sql(sql) > migrator.python(func, *args, **kwargs) > migrator.create_model(Model) > migrator.remove_model(Model, cascade=True) > migrator.add_fields(Model, **fields) > migrator.change_fields(Model, **fields) > migrator.remove_fields(Model, *field_names, cascade=True) > migrator.rename_field(Model, old_field_name, new_field_name) > migrator.rename_table(Model, new_table_name) > migrator.add_index(Model, *col_names, unique=False) > migrator.drop_index(Model, *col_names) > migrator.add_not_null(Model, *field_names) > migrator.drop_not_null(Model, *field_names) > migrator.add_default(Model, field_name, default) Fc dS)zWrite your migrations here.Nmigratordatabasefakekwargss f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/020_malware_scan_types.pymigrater s  Dc dS)z$Write your rollback migrations here.Nrrs r rollbackr s  Dr N)F)__doc__r r rr r rsA,          r defence360agent/migrations/__pycache__/021_add_testing_repo.cpython-311.opt-1.pyc0000644000000000000000000000575700000000000024505 0ustar r_jdddlZddlZddlmZddlmZmZejeZ edZ dZ d e Z dZdZd d Zd d ZdS) N)Path) os_version OsReleaseInfoz(/etc/yum.repos.d/imunify360-testing.repoz1https://repo.imunify360.cloudlinux.com/defense360z{}/RPM-GPG-KEY-CloudLinuxz [imunify360-testing] name=EL-{version} - Imunify360 baseurl={CHECKSITE}/el/{version}/updates-testing/x86_64/ username=defense360 password=nraW!F@\$x4Xd6HHQ enabled=0 gpgcheck=1 gpgkey={RPM_KEY} c |dvr\tsAtt|t t dSdStd|dS)N))version CHECKSITERPM_KEYzVersion {} is not supported) TEST_REPO_PATHexists write_text TEMPLATE_REPOformatr r loggerinfo)r s d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/021_add_testing_repo.py install_repors&$$&&   % %$$#y'%         188AABBBBBFc V|rdS tjtjzrPd}t}|drd}n|drd}t |dSdS#t $r&}td|Yd}~dSd}~wwxYw)N6r7rz)Unable to add imunify360-testing repo: %s) rid_likeRHEL_FEDORA_CENTOSr startswithr Exceptionrwarning)migratordatabasefakekwargsr full_versiones rmigrater$'s  G  " "]%E E "G%<r0s ;;;;;;;;  8 $ $@AA ? % , ,Y 7 7   C C CGGGG$rdefence360agent/migrations/__pycache__/021_add_testing_repo.cpython-311.pyc0000644000000000000000000000575700000000000023546 0ustar r_jdddlZddlZddlmZddlmZmZejeZ edZ dZ d e Z dZdZd d Zd d ZdS) N)Path) os_version OsReleaseInfoz(/etc/yum.repos.d/imunify360-testing.repoz1https://repo.imunify360.cloudlinux.com/defense360z{}/RPM-GPG-KEY-CloudLinuxz [imunify360-testing] name=EL-{version} - Imunify360 baseurl={CHECKSITE}/el/{version}/updates-testing/x86_64/ username=defense360 password=nraW!F@\$x4Xd6HHQ enabled=0 gpgcheck=1 gpgkey={RPM_KEY} c |dvr\tsAtt|t t dSdStd|dS)N))version CHECKSITERPM_KEYzVersion {} is not supported) TEST_REPO_PATHexists write_text TEMPLATE_REPOformatr r loggerinfo)r s d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/021_add_testing_repo.py install_repors&$$&&   % %$$#y'%         188AABBBBBFc V|rdS tjtjzrPd}t}|drd}n|drd}t |dSdS#t $r&}td|Yd}~dSd}~wwxYw)N6r7rz)Unable to add imunify360-testing repo: %s) rid_likeRHEL_FEDORA_CENTOSr startswithr Exceptionrwarning)migratordatabasefakekwargsr full_versiones rmigrater$'s  G  " "]%E E "G%<r0s ;;;;;;;;  8 $ $@AA ? % , ,Y 7 7   C C CGGGG$rdefence360agent/migrations/__pycache__/022_mod_security_vendors_migrations.cpython-311.opt-1.pyc0000644000000000000000000000105000000000000027654 0ustar r_jdZddZddZdS)z No need to user now Fc dSNmigratordatabasefakekwargss s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/022_mod_security_vendors_migrations.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r defence360agent/migrations/__pycache__/022_mod_security_vendors_migrations.cpython-311.pyc0000644000000000000000000000105000000000000026715 0ustar r_jdZddZddZdS)z No need to user now Fc dSNmigratordatabasefakekwargss s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/022_mod_security_vendors_migrations.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r ././@LongLink0000644000000000000000000000015000000000000007767 Lustar defence360agent/migrations/__pycache__/023_add_default_rule_in_modsec_custom_conf.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/023_add_default_rule_in_modsec_custom_conf.cpython-311.opt-1.0000644000000000000000000000411300000000000030344 0ustar r_jLdZddlZddlZddlmZmZdZdZdddZd d Z d d Z dS) zUsing ModSecurity 'WordPress login attempt' rule instead of OSSEC one. This migration is needed in order to add new rule to config after update, because config is non replaceable. N) IConfigFile LocalConfigMOD_SEC_BLOCK_BY_CUSTOM_RULE33332 x)max_incident_repetition check_periodFc |rdSt}tj|jsdStj|jsdSt j|j|jdz|}t| tit<| |ddS)N.oldF)validate) rospathexistsisfileshutilcopyfileconfig_to_dict RULE_VALUES setdefaultSECTIONRULE_IDdict_to_config)migratordatabasefakekwargs local_confignew_confs z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.pymigrater!s  + L 7>>,+ , , 7>>,+ , , OL%|'86'ABBB**,,H0;H$$W-599999c |rdSt}|jdz}tj|rt j||jdSdS)Nr )rrrrrmove)rrrrrolds r rollbackr&s`  + L  f $C w~~c, C*+++++,,r")F) __doc__rr defence360agent.contracts.configrrrrrr!r&r"r r*s EEEEEEEE ( *,cBB  : : : :,,,,,,r"defence360agent/migrations/__pycache__/023_add_default_rule_in_modsec_custom_conf.cpython-311.pyc0000644000000000000000000000411300000000000030121 0ustar r_jLdZddlZddlZddlmZmZdZdZdddZd d Z d d Z dS) zUsing ModSecurity 'WordPress login attempt' rule instead of OSSEC one. This migration is needed in order to add new rule to config after update, because config is non replaceable. N) IConfigFile LocalConfigMOD_SEC_BLOCK_BY_CUSTOM_RULE33332 x)max_incident_repetition check_periodFc |rdSt}tj|jsdStj|jsdSt j|j|jdz|}t| tit<| |ddS)N.oldF)validate) rospathexistsisfileshutilcopyfileconfig_to_dict RULE_VALUES setdefaultSECTIONRULE_IDdict_to_config)migratordatabasefakekwargs local_confignew_confs z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.pymigrater!s  + L 7>>,+ , , 7>>,+ , , OL%|'86'ABBB**,,H0;H$$W-599999c |rdSt}|jdz}tj|rt j||jdSdS)Nr )rrrrrmove)rrrrrolds r rollbackr&s`  + L  f $C w~~c, C*+++++,,r")F) __doc__rr defence360agent.contracts.configrrrrrr!r&r"r r*s EEEEEEEE ( *,cBB  : : : :,,,,,,r"defence360agent/migrations/__pycache__/024_ignore_from_graylist.cpython-311.opt-1.pyc0000644000000000000000000000262300000000000025407 0ustar r_jDddlZGddejZddZddZdS)NcLeZdZejddZGddZdS) IgnoreListTF) primary_keynullceZdZdZdS)IgnoreList.Meta ignore_listN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/024_ignore_from_graylist.pyMetars rrN)r r r pw CharFieldiprrrrrrsR $U 3 3 3B!!!!!!!!!!rrFc :|tdS)zWrite your migrations here.N) create_modelr)migratordatabasefakekwargss rmigrater s *%%%%%rc J|jd}||dS)z$Write your rollback migrations here.r N)orm drop_model)rrrrrs rrollbackrs(m,J  #####r)F)peeweerModelrrrrrrr"so!!!!!!!!&&&& $$$$$$rdefence360agent/migrations/__pycache__/024_ignore_from_graylist.cpython-311.pyc0000644000000000000000000000262300000000000024450 0ustar r_jDddlZGddejZddZddZdS)NcLeZdZejddZGddZdS) IgnoreListTF) primary_keynullceZdZdZdS)IgnoreList.Meta ignore_listN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/024_ignore_from_graylist.pyMetars rrN)r r r pw CharFieldiprrrrrrsR $U 3 3 3B!!!!!!!!!!rrFc :|tdS)zWrite your migrations here.N) create_modelr)migratordatabasefakekwargss rmigrater s *%%%%%rc J|jd}||dS)z$Write your rollback migrations here.r N)orm drop_model)rrrrrs rrollbackrs(m,J  #####r)F)peeweerModelrrrrrrr"so!!!!!!!!&&&& $$$$$$rdefence360agent/migrations/__pycache__/025_malware_config_realtime.cpython-311.opt-1.pyc0000644000000000000000000000302600000000000026021 0ustar r_j2ddlZddlZddlmZddZddZdS)N) LocalConfigFc |rdSt}tj|jsdSt |j5}t j|}dddn #1swxYwY|di}|dd}||d<||d<| |ddS)NMALWARE_SCANNINGenable_scan_uploaded_filesTenable_scan_pure_ftpdenable_scan_modsecF)validate) rospathexistsopenyaml safe_load setdefaultpopdict_to_config) migratordatabasefakekwargs local_configfconfmalware_settingsvalues k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/025_malware_config_realtime.pymigraters  ==L 7>>,+ , , l !A~a  !!!!!!!!!!!!!!!'92>>  !=t D DE05,--2)*u55555s A..A25A2c dS)N)rrrrs rrollbackr sD)F)r r defence360agent.contracts.configrrr rr!rr#s[ 8888886666(      r!defence360agent/migrations/__pycache__/025_malware_config_realtime.cpython-311.pyc0000644000000000000000000000302600000000000025062 0ustar r_j2ddlZddlZddlmZddZddZdS)N) LocalConfigFc |rdSt}tj|jsdSt |j5}t j|}dddn #1swxYwY|di}|dd}||d<||d<| |ddS)NMALWARE_SCANNINGenable_scan_uploaded_filesTenable_scan_pure_ftpdenable_scan_modsecF)validate) rospathexistsopenyaml safe_load setdefaultpopdict_to_config) migratordatabasefakekwargs local_configfconfmalware_settingsvalues k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/025_malware_config_realtime.pymigraters  ==L 7>>,+ , , l !A~a  !!!!!!!!!!!!!!!'92>>  !=t D DE05,--2)*u55555s A..A25A2c dS)N)rrrrs rrollbackr sD)F)r r defence360agent.contracts.configrrr rr!rr#s[ 8888886666(      r!defence360agent/migrations/__pycache__/026_remove_old_temporary_file.cpython-311.opt-1.pyc0000644000000000000000000000246600000000000026426 0ustar r_j9.ddlZddlZddlZddZddZdS)NFc |rdStj}tj|d}tj|rtj|tj|d}tj|D]5}tj|rtj|6dS)Nzpredict_model_description.jsonz imunify360*)tempfile gettempdirospathjoinisfileremoveglob)migratordatabasefakekwargstmp_dirrpatternfilenames m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/026_remove_old_temporary_file.pymigraters !##G 7<<!A B BD w~~d $gll7M22GIg&&   7>>( # # Ih     c dS)N)r r rrs rrollbackrsDr)F)r rrrrrrrrsR     $      rdefence360agent/migrations/__pycache__/026_remove_old_temporary_file.cpython-311.pyc0000644000000000000000000000246600000000000025467 0ustar r_j9.ddlZddlZddlZddZddZdS)NFc |rdStj}tj|d}tj|rtj|tj|d}tj|D]5}tj|rtj|6dS)Nzpredict_model_description.jsonz imunify360*)tempfile gettempdirospathjoinisfileremoveglob)migratordatabasefakekwargstmp_dirrpatternfilenames m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/026_remove_old_temporary_file.pymigraters !##G 7<<!A B BD w~~d $gll7M22GIg&&   7>>( # # Ih     c dS)N)r r rrs rrollbackrsDr)F)r rrrrrrrrsR     $      rdefence360agent/migrations/__pycache__/027_disable_comdo_fp_rules.cpython-311.opt-1.pyc0000644000000000000000000000114600000000000025650 0ustar r_jdZddZddZdS)z\ Current migration doesn't needed, because apache will be restarted in the other migrations Fc dSNmigratordatabasefakekwargss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/027_disable_comdo_fp_rules.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r defence360agent/migrations/__pycache__/027_disable_comdo_fp_rules.cpython-311.pyc0000644000000000000000000000114600000000000024711 0ustar r_jdZddZddZdS)z\ Current migration doesn't needed, because apache will be restarted in the other migrations Fc dSNmigratordatabasefakekwargss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/027_disable_comdo_fp_rules.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r defence360agent/migrations/__pycache__/028_set_permanent_ttl_for_blacklist.cpython-311.opt-1.pyc0000644000000000000000000000165300000000000027616 0ustar r_j^dZddZddZdS)Fc |jd}|t|jdk|jtkzdS)Niplist) expirationBLACK)ormupdate PERMANENT_TTLwherelistnamerexecute)migratordatabasefakekwargs IPListModels s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/028_set_permanent_ttl_for_blacklist.pymigraters^,x(K-0066   (  !] 2 4giiiiic dS)N)r rrrs rrollbackr sDrN)F)r rrrrrrs<       rdefence360agent/migrations/__pycache__/028_set_permanent_ttl_for_blacklist.cpython-311.pyc0000644000000000000000000000165300000000000026657 0ustar r_j^dZddZddZdS)Fc |jd}|t|jdk|jtkzdS)Niplist) expirationBLACK)ormupdate PERMANENT_TTLwherelistnamerexecute)migratordatabasefakekwargs IPListModels s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/028_set_permanent_ttl_for_blacklist.pymigraters^,x(K-0066   (  !] 2 4giiiiic dS)N)r rrrs rrollbackr sDrN)F)r rrrrrrs<       rdefence360agent/migrations/__pycache__/029_custom_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076300000000000024734 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/029_custom_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/029_custom_quarantine.cpython-311.pyc0000644000000000000000000000076300000000000023775 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/029_custom_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/030_rename_max_incident_repetition.cpython-311.opt-1.pyc0000644000000000000000000000401700000000000027412 0ustar r_jslddlmZmZddlmZedefdefdZddZdS) )IConfig LocalConfig)log_error_and_ignoreF config_filec |rdS|}|sdS|di}|dd}|r||d<|di}|D]} | dd}|r|| d< |drD|di|d<|dd} | r|djd i| ||dd dS) NMOD_SEC_BLOCK_BY_SEVERITYmax_incident_repetition max_incidentsMOD_SEC_BLOCK_BY_CUSTOM_RULE INCIDENT_LISTINCIDENT_LOGGING AUTOCLEANUPFT)validate overwrite)config_to_dict setdefaultpopvaluesgetupdatedict_to_config) migratordatabasefakerkwargsconfigblock_by_severityvaluecustom_rule_listcustom_rule_confauto_cleanup_confs r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/030_rename_max_incident_repetition.pymigrater$sZ   ' ' ) )F ))*ErJJ  ! !";T B BE 3-2/*(()GLL,335566 $$%>EE  605 _ - zz/""C%+ZZ%D%D!""JJ}d;;  C -F% & - B B0A B B BvFFFFFc dS)Nr)rrrrs r#rollbackr'+sDr%N)F) defence360agent.contracts.configrrdefence360agent.utilsrr$r'rr%r#r*sAAAAAAAA666666 &;== "G"G "G"G"G"GJ      r%defence360agent/migrations/__pycache__/030_rename_max_incident_repetition.cpython-311.pyc0000644000000000000000000000401700000000000026453 0ustar r_jslddlmZmZddlmZedefdefdZddZdS) )IConfig LocalConfig)log_error_and_ignoreF config_filec |rdS|}|sdS|di}|dd}|r||d<|di}|D]} | dd}|r|| d< |drD|di|d<|dd} | r|djd i| ||dd dS) NMOD_SEC_BLOCK_BY_SEVERITYmax_incident_repetition max_incidentsMOD_SEC_BLOCK_BY_CUSTOM_RULE INCIDENT_LISTINCIDENT_LOGGING AUTOCLEANUPFT)validate overwrite)config_to_dict setdefaultpopvaluesgetupdatedict_to_config) migratordatabasefakerkwargsconfigblock_by_severityvaluecustom_rule_listcustom_rule_confauto_cleanup_confs r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/030_rename_max_incident_repetition.pymigrater$sZ   ' ' ) )F ))*ErJJ  ! !";T B BE 3-2/*(()GLL,335566 $$%>EE  605 _ - zz/""C%+ZZ%D%D!""JJ}d;;  C -F% & - B B0A B B BvFFFFFc dS)Nr)rrrrs r#rollbackr'+sDr%N)F) defence360agent.contracts.configrrdefence360agent.utilsrr$r'rr%r#r*sAAAAAAAA666666 &;== "G"G "G"G"G"GJ      r%defence360agent/migrations/__pycache__/031_add_mode_field.cpython-311.opt-1.pyc0000644000000000000000000000207300000000000024057 0ustar r_jFddlZddlZejeZddZddZdS)NFc t|jd}||tjddS)zWrite your migrations here. malware_hitsT)null)modeN)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareHitss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_add_mode_field.pymigrater s<,~.K  "/t*D*D*DEEEEEc L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackrs*,~.K ;/////r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $FFFF000000rdefence360agent/migrations/__pycache__/031_add_mode_field.cpython-311.pyc0000644000000000000000000000207300000000000023120 0ustar r_jFddlZddlZejeZddZddZdS)NFc t|jd}||tjddS)zWrite your migrations here. malware_hitsT)null)modeN)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareHitss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_add_mode_field.pymigrater s<,~.K  "/t*D*D*DEEEEEc L|jd}||ddS)z$Write your rollback migrations here.rrN)r remove_fieldsr s rrollbackrs*,~.K ;/////r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $FFFF000000rdefence360agent/migrations/__pycache__/031_modsec_config_for_plesk_include.cpython-311.opt-1.pyc0000644000000000000000000000445200000000000027531 0ustar r_jddlmZddlmZddlmZeeZejddZejddZ dS) ) getLogger)run_coro)antivirus_modeFc  ddlm}ddlm}n#t$rYdSwxYw |s5|r!t |sdS|ddl m }|dS#t$r3}t dt|Yd}~dSd}~wwxYwNr)Plesk)ModSecSettings)graceful_restart_syncz"Error during web-server update: %s)im360.subsys.panels.pleskr&im360.subsys.panels.plesk.mod_securityr ImportError is_installedrinstalled_modsecinclude_modsec_conf!defence360agent.subsys.web_serverr Exceptionloggerwarningstrmigratordatabasefakekwargsrr r es s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_modsec_config_for_plesk_include.pymigrater 333333IIIIIII  E  %%'' E224455  F**,,,KKKKKK EEE;SVVDDDDDDDDDE)  7B$B B= (B88B=c  ddlm}ddlm}n#t$rYdSwxYw |s5|r!t |sdS|ddl m }|dS#t$r3}t dt|Yd}~dSd}~wwxYwr)r rr r r rrrrevert_conf_includerr rrrrrs rrollbackr"!rrN)F) loggingrdefence360agent.utilsrr__name__rskiprr"rr)s******000000 8  EEEE.EEEEEEr(defence360agent/migrations/__pycache__/031_modsec_config_for_plesk_include.cpython-311.pyc0000644000000000000000000000445200000000000026572 0ustar r_jddlmZddlmZddlmZeeZejddZejddZ dS) ) getLogger)run_coro)antivirus_modeFc  ddlm}ddlm}n#t$rYdSwxYw |s5|r!t |sdS|ddl m }|dS#t$r3}t dt|Yd}~dSd}~wwxYwNr)Plesk)ModSecSettings)graceful_restart_syncz"Error during web-server update: %s)im360.subsys.panels.pleskr&im360.subsys.panels.plesk.mod_securityr ImportError is_installedrinstalled_modsecinclude_modsec_conf!defence360agent.subsys.web_serverr Exceptionloggerwarningstrmigratordatabasefakekwargsrr r es s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/031_modsec_config_for_plesk_include.pymigrater 333333IIIIIII  E  %%'' E224455  F**,,,KKKKKK EEE;SVVDDDDDDDDDE)  7B$B B= (B88B=c  ddlm}ddlm}n#t$rYdSwxYw |s5|r!t |sdS|ddl m }|dS#t$r3}t dt|Yd}~dSd}~wwxYwr)r rr r r rrrrevert_conf_includerr rrrrrs rrollbackr"!rrN)F) loggingrdefence360agent.utilsrr__name__rskiprr"rr)s******000000 8  EEEE.EEEEEEr(defence360agent/migrations/__pycache__/032_chmod_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076200000000000024505 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/032_chmod_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/032_chmod_quarantine.cpython-311.pyc0000644000000000000000000000076200000000000023546 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/032_chmod_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/033_disable_cphulk.cpython-311.opt-1.pyc0000644000000000000000000000272500000000000024137 0ustar r_j}ZddlZddlZddlmZddlmZeeZdZddZ ddZ dS) N) Packaging) getLoggerc tjdtjzdgdS#tj$r%}t |Yd}~dSd}~wwxYw)Nz %s/scripts/disable_3rd_party_idsz --nocheck) subprocess check_callrDATADIRCalledProcessErrorloggererror)es b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/033_disable_cphulk.pydisable_3rdpartyr sx2Y5FF       ( Qs#'AAAFc ||s)tjtjsdSt dSN)ospathisfilerrrmigratordatabasefakekwargss r migraters9 27>>)"344c dSrrs r rollbackrsDr)F) rr defence360agent.contracts.configrloggingr__name__r rrrrrr r!s 666666 8           rdefence360agent/migrations/__pycache__/033_disable_cphulk.cpython-311.pyc0000644000000000000000000000272500000000000023200 0ustar r_j}ZddlZddlZddlmZddlmZeeZdZddZ ddZ dS) N) Packaging) getLoggerc tjdtjzdgdS#tj$r%}t |Yd}~dSd}~wwxYw)Nz %s/scripts/disable_3rd_party_idsz --nocheck) subprocess check_callrDATADIRCalledProcessErrorloggererror)es b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/033_disable_cphulk.pydisable_3rdpartyr sx2Y5FF       ( Qs#'AAAFc ||s)tjtjsdSt dSN)ospathisfilerrrmigratordatabasefakekwargss r migraters9 27>>)"344c dSrrs r rollbackrsDr)F) rr defence360agent.contracts.configrloggingr__name__r rrrrrr r!s 666666 8           rdefence360agent/migrations/__pycache__/034_hits_extras.cpython-311.opt-1.pyc0000644000000000000000000000360700000000000023524 0ustar r_j FddlZddlZejeZddZddZdS)NFc |jdGfddtj}||dS)zWrite your migrations here. malware_hitsceZdZGddZejdZejddZej dZ ej dZ d S) migrate..MalwareHitExtraceZdZdZdS)%migrate..MalwareHitExtra.Metamalware_hit_extrasN)__name__ __module__ __qualname__db_table_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/034_hits_extras.pyMetars+HHHrrT) primary_keyFextras)null related_name)rN) r r r rpw IntegerFieldidForeignKeyFieldhit CharFieldnamevalue) MalwareHitsrMalwareHitExtrars , , , , , , , ,R_ . . . b %hOOOr|''' %(((rrN)ormrModel create_model)migratordatabasefakekwargsrrs @rmigrater' scn-J)))))))"())) /*****rc J|jd}||dS)z$Write your rollback migrations here.r N)r remove_model)r#r$r%r&rs rrollbackr*s)l#78O /*****r)F)loggingpeeweer getLoggerr loggerr'r*rrrr/s`  8 $ $++++"++++++rdefence360agent/migrations/__pycache__/034_hits_extras.cpython-311.pyc0000644000000000000000000000360700000000000022565 0ustar r_j FddlZddlZejeZddZddZdS)NFc |jdGfddtj}||dS)zWrite your migrations here. malware_hitsceZdZGddZejdZejddZej dZ ej dZ d S) migrate..MalwareHitExtraceZdZdZdS)%migrate..MalwareHitExtra.Metamalware_hit_extrasN)__name__ __module__ __qualname__db_table_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/034_hits_extras.pyMetars+HHHrrT) primary_keyFextras)null related_name)rN) r r r rpw IntegerFieldidForeignKeyFieldhit CharFieldnamevalue) MalwareHitsrMalwareHitExtrars , , , , , , , ,R_ . . . b %hOOOr|''' %(((rrN)ormrModel create_model)migratordatabasefakekwargsrrs @rmigrater' scn-J)))))))"())) /*****rc J|jd}||dS)z$Write your rollback migrations here.r N)r remove_model)r#r$r%r&rs rrollbackr*s)l#78O /*****r)F)loggingpeeweer getLoggerr loggerr'r*rrrr/s`  8 $ $++++"++++++rdefence360agent/migrations/__pycache__/035_add_dos_expiration_field.cpython-311.opt-1.pyc0000644000000000000000000000166000000000000026167 0ustar r_jhddlZddZddZdS)NFc v|jd}||tjdddS)NiplistrT)defaultnull)dos_expiration)orm add_fieldspw IntegerFieldmigratordatabasefakekwargsIPLists l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/035_add_dos_expiration_field.pymigratersI \( #F rqtDDDc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+ \( #F 6#344444r)F)peeweer rrrrrsC555555rdefence360agent/migrations/__pycache__/035_add_dos_expiration_field.cpython-311.pyc0000644000000000000000000000166000000000000025230 0ustar r_jhddlZddZddZdS)NFc v|jd}||tjdddS)NiplistrT)defaultnull)dos_expiration)orm add_fieldspw IntegerFieldmigratordatabasefakekwargsIPLists l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/035_add_dos_expiration_field.pymigratersI \( #F rqtDDDc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+ \( #F 6#344444r)F)peeweer rrrrrsC555555rdefence360agent/migrations/__pycache__/036_add_block_port.cpython-311.opt-1.pyc0000644000000000000000000000605600000000000024140 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NceZdZdZejdZejdejdgZ ejdZ GddZ d S) BlockedPortz+ Port + protocol for blocking data Fnullzproto in ('tcp', 'udp', 'all'))r constraintsTceZdZdZdZdS)BlockedPort.Meta blocked_port)))portprotoTN__name__ __module__ __qualname__db_tableindexesb/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/036_add_block_port.pyMetar s! rrN) rrr__doc__pw IntegerFieldr CharFieldCheckr commentrrrrrrs 2? & & &D BL *J!K!K L   Ebl%%%G          rrceZdZdZejedddZejdZ ejdZ Gdd Z d S) IgnoredByPortz) Ignored IPs for port + protocol FCASCADEips)r on_delete related_namerTceZdZdZdZdS)IgnoredByPort.Metaignored_by_port_proto))) port_protoipTNr rrrrr$$s* rrN) rrrrrForeignKeyFieldrr&rr'rrrrrrrs$#%95J 5 ! ! !Bbl%%%G          rrFc |t|t|jd}||t jddS)NiplistTr) full_access) create_modelrrorm add_fieldsr BooleanField)migratordatabasefakekwargsIPLists rmigrater5-sb +&&& -((( \( #F BO,F,F,FGGGGGrc |jd}|jd}|jd}||||||ddS)Nr blocked_port_ipr*r+)r- remove_model remove_fields)r0r1r2r3rrr4s rrollbackr:5sk,~.KL!23M \( #F +&&& -((( 6=11111r)F)peeweerModelrrr5r:rrrr=s     "(   *     BH   (HHHH222222rdefence360agent/migrations/__pycache__/036_add_block_port.cpython-311.pyc0000644000000000000000000000605600000000000023201 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NceZdZdZejdZejdejdgZ ejdZ GddZ d S) BlockedPortz+ Port + protocol for blocking data Fnullzproto in ('tcp', 'udp', 'all'))r constraintsTceZdZdZdZdS)BlockedPort.Meta blocked_port)))portprotoTN__name__ __module__ __qualname__db_tableindexesb/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/036_add_block_port.pyMetar s! rrN) rrr__doc__pw IntegerFieldr CharFieldCheckr commentrrrrrrs 2? & & &D BL *J!K!K L   Ebl%%%G          rrceZdZdZejedddZejdZ ejdZ Gdd Z d S) IgnoredByPortz) Ignored IPs for port + protocol FCASCADEips)r on_delete related_namerTceZdZdZdZdS)IgnoredByPort.Metaignored_by_port_proto))) port_protoipTNr rrrrr$$s* rrN) rrrrrForeignKeyFieldrr&rr'rrrrrrrs$#%95J 5 ! ! !Bbl%%%G          rrFc |t|t|jd}||t jddS)NiplistTr) full_access) create_modelrrorm add_fieldsr BooleanField)migratordatabasefakekwargsIPLists rmigrater5-sb +&&& -((( \( #F BO,F,F,FGGGGGrc |jd}|jd}|jd}||||||ddS)Nr blocked_port_ipr*r+)r- remove_model remove_fields)r0r1r2r3rrr4s rrollbackr:5sk,~.KL!23M \( #F +&&& -((( 6=11111r)F)peeweerModelrrr5r:rrrr=s     "(   *     BH   (HHHH222222rdefence360agent/migrations/__pycache__/037_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000544500000000000024155 0ustar r_j|FddlZddlZejeZddZddZdS)NFc GddtjGfddtj}|||dS)zWrite your migrations here.ceZdZGddZejZejdZejdZ ej dZ dS)migrate..DisabledRuleceZdZdZdZdS)"migrate..DisabledRule.Metadisabled_rules)))pluginrule_idTN)__name__ __module__ __qualname__db_tableindexesb/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/037_disabled_rules.pyMetar s'H6GGGrrFnullN) r r r rpwPrimaryKeyFieldid CharFieldr r TextFieldnamerrr DisabledRuler s 7 7 7 7 7 7 7 7 R  ! !5)))",E***r|'''rrcreZdZejddZejdZGddZdS) #migrate..DisabledRuleDomaindomainsCASCADE)backref on_deleteFrc4eZdZdZejddZdS)(migrate..DisabledRuleDomain.Metadisabled_rules_domainsdisabled_rule_id_iddomainN)r r r rr CompositeKey primary_keyrrrrr$s'/H)"/*?JJKKKrrN) r r r rForeignKeyFieldr&rr'r)rsrDisabledRuleDomainrs~0b0 )y   5))) K K K K K K K K K Krr+N)rModel create_model)migratordatabasefakekwargsr+rs @rmigrater2 s(((((rx(((KKKKKKKRXKKK ,''' ,-----rc ||jd||jddS)z$Write your rollback migrations here.r%rN) remove_modelorm)r.r/r0r1s rrollbackr6$s@ (,'?@AAA (,'7899999r)F)loggingpeeweer getLoggerr loggerr2r6rrrr;s`  8 $ $....6::::::rdefence360agent/migrations/__pycache__/037_disabled_rules.cpython-311.pyc0000644000000000000000000000544500000000000023216 0ustar r_j|FddlZddlZejeZddZddZdS)NFc GddtjGfddtj}|||dS)zWrite your migrations here.ceZdZGddZejZejdZejdZ ej dZ dS)migrate..DisabledRuleceZdZdZdZdS)"migrate..DisabledRule.Metadisabled_rules)))pluginrule_idTN)__name__ __module__ __qualname__db_tableindexesb/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/037_disabled_rules.pyMetar s'H6GGGrrFnullN) r r r rpwPrimaryKeyFieldid CharFieldr r TextFieldnamerrr DisabledRuler s 7 7 7 7 7 7 7 7 R  ! !5)))",E***r|'''rrcreZdZejddZejdZGddZdS) #migrate..DisabledRuleDomaindomainsCASCADE)backref on_deleteFrc4eZdZdZejddZdS)(migrate..DisabledRuleDomain.Metadisabled_rules_domainsdisabled_rule_id_iddomainN)r r r rr CompositeKey primary_keyrrrrr$s'/H)"/*?JJKKKrrN) r r r rForeignKeyFieldr&rr'r)rsrDisabledRuleDomainrs~0b0 )y   5))) K K K K K K K K K Krr+N)rModel create_model)migratordatabasefakekwargsr+rs @rmigrater2 s(((((rx(((KKKKKKKRXKKK ,''' ,-----rc ||jd||jddS)z$Write your rollback migrations here.r%rN) remove_modelorm)r.r/r0r1s rrollbackr6$s@ (,'?@AAA (,'7899999r)F)loggingpeeweer getLoggerr loggerr2r6rrrr;s`  8 $ $....6::::::rdefence360agent/migrations/__pycache__/038_disabled_rules_import.cpython-311.opt-1.pyc0000644000000000000000000000252200000000000025541 0ustar r_jhddlZddlmZmZejeZdefdefdZddZdS)N)IConfig LocalConfigF config_filec |rdS|}|sdS|di|didg||dddS)zWrite your migrations here.NOSSECMOD_SEC_BLOCK_BY_SEVERITYignoreTF) overwritevalidate)config_to_dictpopgetdict_to_config)migratordatabasefakerkwargsconfigs i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/038_disabled_rules_import.pymigraters   ' ' ) )F  JJw JJ*B//33HbAAAvFFFFFc dS)z$Write your rollback migrations here.N)rrrrs rrollbackr sDr)F) logging defence360agent.contracts.configrr getLogger__name__loggerrrrrrr sAAAAAAAA  8 $ $ &;== GG GGGG0      rdefence360agent/migrations/__pycache__/038_disabled_rules_import.cpython-311.pyc0000644000000000000000000000252200000000000024602 0ustar r_jhddlZddlmZmZejeZdefdefdZddZdS)N)IConfig LocalConfigF config_filec |rdS|}|sdS|di|didg||dddS)zWrite your migrations here.NOSSECMOD_SEC_BLOCK_BY_SEVERITYignoreTF) overwritevalidate)config_to_dictpopgetdict_to_config)migratordatabasefakerkwargsconfigs i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/038_disabled_rules_import.pymigraters   ' ' ) )F  JJw JJ*B//33HbAAAvFFFFFc dS)z$Write your rollback migrations here.N)rrrrs rrollbackr sDr)F) logging defence360agent.contracts.configrr getLogger__name__loggerrrrrrr sAAAAAAAA  8 $ $ &;== GG GGGG0      rdefence360agent/migrations/__pycache__/039_fix_malware_hits.cpython-311.opt-1.pyc0000644000000000000000000000273000000000000024515 0ustar r_j>ddlZejeZddZddZdS)NFc |d|d|d|ddS)zWrite your migrations here.a CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" VARCHAR(255) NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) z7INSERT INTO malware_hits_new SELECT * FROM malware_hitszDROP TABLE malware_hitsz3ALTER TABLE malware_hits_new RENAME TO malware_hitsN)sqlmigratordatabasefakekwargss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/039_fix_malware_hits.pymigrater s` LL     LLJKKK LL*+++ LLFGGGGGc dS)z$Write your rollback migrations here.Nrs r rollbackrsDr )F)logging getLogger__name__loggerr rrr r rsX  8 $ $HHHH*      r defence360agent/migrations/__pycache__/039_fix_malware_hits.cpython-311.pyc0000644000000000000000000000273000000000000023556 0ustar r_j>ddlZejeZddZddZdS)NFc |d|d|d|ddS)zWrite your migrations here.a CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" VARCHAR(255) NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) z7INSERT INTO malware_hits_new SELECT * FROM malware_hitszDROP TABLE malware_hitsz3ALTER TABLE malware_hits_new RENAME TO malware_hitsN)sqlmigratordatabasefakekwargss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/039_fix_malware_hits.pymigrater s` LL     LLJKKK LL*+++ LLFGGGGGc dS)z$Write your rollback migrations here.Nrs r rollbackrsDr )F)logging getLogger__name__loggerr rrr r rsX  8 $ $HHHH*      r defence360agent/migrations/__pycache__/040_ignore_mod_sec_rule_214920.cpython-311.opt-1.pyc0000644000000000000000000000105500000000000026003 0ustar r_jdZddZddZdS)z#Migration was buggy, so skipping itFc dSNmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/040_ignore_mod_sec_rule_214920.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rs=))          r defence360agent/migrations/__pycache__/040_ignore_mod_sec_rule_214920.cpython-311.pyc0000644000000000000000000000105500000000000025044 0ustar r_jdZddZddZdS)z#Migration was buggy, so skipping itFc dSNmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/040_ignore_mod_sec_rule_214920.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rs=))          r defence360agent/migrations/__pycache__/041_fix_invalid_ignore_filed.cpython-311.opt-1.pyc0000644000000000000000000000111600000000000026160 0ustar r_jdZddZddZdS)zFAdding 214920 rule to ignored on disabled rules level in 038 migrationFc dSNmigratordatabasefakekwargss l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/041_fix_invalid_ignore_filed.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rs=LL          r defence360agent/migrations/__pycache__/041_fix_invalid_ignore_filed.cpython-311.pyc0000644000000000000000000000111600000000000025221 0ustar r_jdZddZddZdS)zFAdding 214920 rule to ignored on disabled rules level in 038 migrationFc dSNmigratordatabasefakekwargss l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/041_fix_invalid_ignore_filed.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rs=LL          r defence360agent/migrations/__pycache__/042_rebuildinstalledssldb.cpython-311.opt-1.pyc0000644000000000000000000000301100000000000025531 0ustar r_juddlZddlZddlmZejeZejddZejddZ dS)N)antivirus_modeFc |rdS ddlm}n#t$rYdSwxYw|rX t jdgdS#t $r3}tdt|Yd}~dSd}~wwxYwdS)Nr)cPanelz/scripts/rebuildinstalledssldbz#Failed to rebuild cpanel ssl db: %s) im360.subsys.panels.cpanelr ImportError is_installed subprocessrun Exceptionloggerwarningstr)migratordatabasefakekwargsres i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/042_rebuildinstalledssldb.pymigrater s 5555555 J J N<= > > > > > J J J NN@#a&& I I I I I I I I I JJJs# A B(BBc dS)N)rrrrs rrollbackrsD)F) loggingr defence360agent.utilsr getLogger__name__r skiprrrrrrs000000  8 $ $ J J J J      rdefence360agent/migrations/__pycache__/042_rebuildinstalledssldb.cpython-311.pyc0000644000000000000000000000301100000000000024572 0ustar r_juddlZddlZddlmZejeZejddZejddZ dS)N)antivirus_modeFc |rdS ddlm}n#t$rYdSwxYw|rX t jdgdS#t $r3}tdt|Yd}~dSd}~wwxYwdS)Nr)cPanelz/scripts/rebuildinstalledssldbz#Failed to rebuild cpanel ssl db: %s) im360.subsys.panels.cpanelr ImportError is_installed subprocessrun Exceptionloggerwarningstr)migratordatabasefakekwargsres i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/042_rebuildinstalledssldb.pymigrater s 5555555 J J N<= > > > > > J J J NN@#a&& I I I I I I I I I JJJs# A B(BBc dS)N)rrrrs rrollbackrsD)F) loggingr defence360agent.utilsr getLogger__name__r skiprrrrrrs000000  8 $ $ J J J J      rdefence360agent/migrations/__pycache__/043_disable_dos_scan_by_default.cpython-311.opt-1.pyc0000644000000000000000000000177000000000000026640 0ustar r_j"ddlmZddZddZdS)) ConfigFileFc |rdSt}|}|sdS|di}d|d<||ddS)NDOSFenabled)validate)rconfig_to_dict setdefaultdict_to_config)migratordatabasefakekwargs config_fileconfig dos_settingss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/043_disable_dos_scan_by_default.pymigraterst ,,K  ' ' ) )F $$UB//L#Lv66666c dS)N)r r r rs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrsI777777 7 7 7 7      rdefence360agent/migrations/__pycache__/043_disable_dos_scan_by_default.cpython-311.pyc0000644000000000000000000000177000000000000025701 0ustar r_j"ddlmZddZddZdS)) ConfigFileFc |rdSt}|}|sdS|di}d|d<||ddS)NDOSFenabled)validate)rconfig_to_dict setdefaultdict_to_config)migratordatabasefakekwargs config_fileconfig dos_settingss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/043_disable_dos_scan_by_default.pymigraterst ,,K  ' ' ) )F $$UB//L#Lv66666c dS)N)r r r rs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrsI777777 7 7 7 7      rdefence360agent/migrations/__pycache__/044_ignore_virtfs_on_cpanel.cpython-311.opt-1.pyc0000644000000000000000000000200300000000000026053 0ustar r_j&dZddlmZddZddZdS)z[ This migration adds cpanel virtfs directory (/home/virtfs) to ignore for malware scanning )cPanelFc ~|s8tjr'|jd}|ddSdSdS)Nmalware_ignore_pathz /home/virtfs)path)r is_installedorm get_or_create)migratordatabasefakekwargsMalwareIgnorePaths k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/044_ignore_virtfs_on_cpanel.pymigratersY =f)++=$L)>?''^'<<<<<====c dS)N)r r r r s rrollbackrsDrN)F)__doc__$defence360agent.subsys.panels.cpanelrrrrrrrsU877777====       rdefence360agent/migrations/__pycache__/044_ignore_virtfs_on_cpanel.cpython-311.pyc0000644000000000000000000000200300000000000025114 0ustar r_j&dZddlmZddZddZdS)z[ This migration adds cpanel virtfs directory (/home/virtfs) to ignore for malware scanning )cPanelFc ~|s8tjr'|jd}|ddSdSdS)Nmalware_ignore_pathz /home/virtfs)path)r is_installedorm get_or_create)migratordatabasefakekwargsMalwareIgnorePaths k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/044_ignore_virtfs_on_cpanel.pymigratersY =f)++=$L)>?''^'<<<<<====c dS)N)r r r r s rrollbackrsDrN)F)__doc__$defence360agent.subsys.panels.cpanelrrrrrrrsU877777====       rdefence360agent/migrations/__pycache__/045_ignore_vdserver_dir_in_csf.cpython-311.opt-1.pyc0000644000000000000000000000157500000000000026555 0ustar r_jS.ddlZddlmZdZddZddZdS)N)append_with_newlinez/etc/csf/csf.fignoreFc |s;tjtrt tddSdSdS)Nz/tmp/.vdserver )ospathisfile CSF_FIGNORErmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/045_ignore_vdserver_dir_in_csf.pymigratersJ =bgnn[11=K);<<<<<====c dS)Nr s rrollbackr sDr)F)rdefence360agent.utilsrrrrrrrrsW 555555$ ====       rdefence360agent/migrations/__pycache__/045_ignore_vdserver_dir_in_csf.cpython-311.pyc0000644000000000000000000000157500000000000025616 0ustar r_jS.ddlZddlmZdZddZddZdS)N)append_with_newlinez/etc/csf/csf.fignoreFc |s;tjtrt tddSdSdS)Nz/tmp/.vdserver )ospathisfile CSF_FIGNORErmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/045_ignore_vdserver_dir_in_csf.pymigratersJ =bgnn[11=K);<<<<<====c dS)Nr s rrollbackr sDr)F)rdefence360agent.utilsrrrrrrrrsW 555555$ ====       rdefence360agent/migrations/__pycache__/046_foreign_key_fix.cpython-311.opt-1.pyc0000644000000000000000000000264300000000000024340 0ustar r_jk>ddlZejeZddZddZdS)NFc |d|d|d|ddS)zWrite your migrations here.aB CREATE TABLE "malware_hit_extras_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "hit_id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "value" VARCHAR(255) NOT NULL, FOREIGN KEY ("hit_id") REFERENCES "malware_hits" ("id") ON DELETE CASCADE ) zCINSERT INTO malware_hit_extras_new SELECT * FROM malware_hit_extraszDROP TABLE malware_hit_extrasz?ALTER TABLE malware_hit_extras_new RENAME TO malware_hit_extrasN)sqlmigratordatabasefakekwargss c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/046_foreign_key_fix.pymigrater sr LL     LLM LL0111 LLIc dS)z$Write your rollback migrations here.Nrs r rollbackrsDr )F)logging getLogger__name__loggerr rrr r rsT  8 $ $0      r defence360agent/migrations/__pycache__/046_foreign_key_fix.cpython-311.pyc0000644000000000000000000000264300000000000023401 0ustar r_jk>ddlZejeZddZddZdS)NFc |d|d|d|ddS)zWrite your migrations here.aB CREATE TABLE "malware_hit_extras_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "hit_id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "value" VARCHAR(255) NOT NULL, FOREIGN KEY ("hit_id") REFERENCES "malware_hits" ("id") ON DELETE CASCADE ) zCINSERT INTO malware_hit_extras_new SELECT * FROM malware_hit_extraszDROP TABLE malware_hit_extrasz?ALTER TABLE malware_hit_extras_new RENAME TO malware_hit_extrasN)sqlmigratordatabasefakekwargss c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/046_foreign_key_fix.pymigrater sr LL     LLM LL0111 LLIc dS)z$Write your rollback migrations here.Nrs r rollbackrsDr )F)logging getLogger__name__loggerr rrr r rsT  8 $ $0      r defence360agent/migrations/__pycache__/047_license_in_file.cpython-311.opt-1.pyc0000644000000000000000000000250200000000000024273 0ustar r_j@.ddlZddlmZdZddZddZdS)N) model_to_dictz /var/imunify360/license_old.jsonFc (|rdS|jd}|ddd\}}ttd5}t jt ||dddn #1swxYwY||dS)NlicenseTr)status expiration)defaultsw)orm get_or_createopenFALLBACK_LICENSE_FILEjsondumpr remove_model)migratordatabasefakekwargs LicenseModellic_fs c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/047_license_in_file.pymigraters < *L  ' '  (FC #S ) ))Q -$$a((())))))))))))))) ,'''''s#A22A69A6c dS)N)rrrrs rrollbackrsD)F)rplayhouse.shortcutsrr rrrrrr sX ------: ( ( ( (      rdefence360agent/migrations/__pycache__/047_license_in_file.cpython-311.pyc0000644000000000000000000000250200000000000023334 0ustar r_j@.ddlZddlmZdZddZddZdS)N) model_to_dictz /var/imunify360/license_old.jsonFc (|rdS|jd}|ddd\}}ttd5}t jt ||dddn #1swxYwY||dS)NlicenseTr)status expiration)defaultsw)orm get_or_createopenFALLBACK_LICENSE_FILEjsondumpr remove_model)migratordatabasefakekwargs LicenseModellic_fs c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/047_license_in_file.pymigraters < *L  ' '  (FC #S ) ))Q -$$a((())))))))))))))) ,'''''s#A22A69A6c dS)N)rrrrs rrollbackrsD)F)rplayhouse.shortcutsrr rrrrrr sX ------: ( ( ( (      rdefence360agent/migrations/__pycache__/048_malware_hits_vendor_field.cpython-311.opt-1.pyc0000644000000000000000000000203600000000000026366 0ustar r_jFddlZddlZejeZddZddZdS)NFc v|jd}||tjdddS)N malware_hitsFclamav)nulldefault)vendor)orm add_fieldspw CharFieldmigratordatabasefakekwargs MalwareHitss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/048_malware_hits_vendor_field.pymigrater sI,~.K BLeXFFFc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs*,~.K ;11111r)F)loggingpeeweer getLogger__name__loggerrrrrrs`  8 $ $222222rdefence360agent/migrations/__pycache__/048_malware_hits_vendor_field.cpython-311.pyc0000644000000000000000000000203600000000000025427 0ustar r_jFddlZddlZejeZddZddZdS)NFc v|jd}||tjdddS)N malware_hitsFclamav)nulldefault)vendor)orm add_fieldspw CharFieldmigratordatabasefakekwargs MalwareHitss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/048_malware_hits_vendor_field.pymigrater sI,~.K BLeXFFFc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs*,~.K ;11111r)F)loggingpeeweer getLogger__name__loggerrrrrrs`  8 $ $222222rdefence360agent/migrations/__pycache__/049_add_auto_added_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000216300000000000027323 0ustar r_j"dZddlZddZddZdS)z Introducing new filed `auto_whitelisted` in order to mark IPs that were autowhitelied during `--remote-addr` flag. This will help to differentiate such IPs in UI. NFc v|jd}||tjdddS)NiplistFT)defaultnull)auto_whitelisted)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/049_add_auto_added_field_to_iplist.pymigrater sI \( #F T!J!J!Jc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs+ \( #F 6#566666r)F)__doc__peeweer rrrrrsO 777777rdefence360agent/migrations/__pycache__/049_add_auto_added_field_to_iplist.cpython-311.pyc0000644000000000000000000000216300000000000026364 0ustar r_j"dZddlZddZddZdS)z Introducing new filed `auto_whitelisted` in order to mark IPs that were autowhitelied during `--remote-addr` flag. This will help to differentiate such IPs in UI. NFc v|jd}||tjdddS)NiplistFT)defaultnull)auto_whitelisted)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/049_add_auto_added_field_to_iplist.pymigrater sI \( #F T!J!J!Jc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs+ \( #F 6#566666r)F)__doc__peeweer rrrrrsO 777777rdefence360agent/migrations/__pycache__/050_fill_auto_whitelisted.cpython-311.opt-1.pyc0000644000000000000000000000202000000000000025534 0ustar r_jdZddZddZdS)zw Filling `auto_whitelisted` filed that was added in previous 049 migration. Matching IPs that were auto added earlier. Fc |jd}|d|jddS)NiplistT)auto_whitelistedzIP auto-whitelisted with)ormupdatewherecomment startswithexecute)migratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/050_fill_auto_whitelisted.pymigratersR \( #F MM4M((..!!"<== giiiiic dS)N)r r r rs rrollbackrsDrN)F)__doc__rrrrrrsA       rdefence360agent/migrations/__pycache__/050_fill_auto_whitelisted.cpython-311.pyc0000644000000000000000000000202000000000000024575 0ustar r_jdZddZddZdS)zw Filling `auto_whitelisted` filed that was added in previous 049 migration. Matching IPs that were auto added earlier. Fc |jd}|d|jddS)NiplistT)auto_whitelistedzIP auto-whitelisted with)ormupdatewherecomment startswithexecute)migratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/050_fill_auto_whitelisted.pymigratersR \( #F MM4M((..!!"<== giiiiic dS)N)r r r rs rrollbackrsDrN)F)__doc__rrrrrrsA       rdefence360agent/migrations/__pycache__/051_cleanup_vd_license.cpython-311.opt-1.pyc0000644000000000000000000000462100000000000025005 0ustar r_jlddlZddlZddlmZejeZGddZddZddZ dS) N)suppressceZdZdZdZdZdS)VirusdieLicensez/usr/local/vdserver/config.jsonc0|ddS)N) _write_key)selfs f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/051_cleanup_vd_license.py unregisterzVirusdieLicense.unregister s ct|j5}tj|}dddn #1swxYwY||d<t|jd5}tj||dddddddS#1swxYwYdS)N vdbApiKeywT),z: ) sort_keysindent separators)open CONFIG_FILEjsonloaddump)r key read_filecontent write_files r rzVirusdieLicense._write_keys $" # # +yi **G + + + + + + + + + + + + + + + #  $"C ( ( J I&                       s6::BB BN)__name__ __module__ __qualname__rr rr r rrs73K     r rFc tt5tddddS#1swxYwYdS)zWrite your migrations here.N)rFileNotFoundErrorrr migratordatabasefakekwargss r migrater)s # $ $''$$&&&''''''''''''''''''s!AA Ac dS)z$Write your rollback migrations here.Nr!r$s r rollbackr+%sDr )F) rlogging contextlibr getLoggerrloggerrr)r+r!r r r0s   8 $ $,''''      r defence360agent/migrations/__pycache__/051_cleanup_vd_license.cpython-311.pyc0000644000000000000000000000462100000000000024046 0ustar r_jlddlZddlZddlmZejeZGddZddZddZ dS) N)suppressceZdZdZdZdZdS)VirusdieLicensez/usr/local/vdserver/config.jsonc0|ddS)N) _write_key)selfs f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/051_cleanup_vd_license.py unregisterzVirusdieLicense.unregister s ct|j5}tj|}dddn #1swxYwY||d<t|jd5}tj||dddddddS#1swxYwYdS)N vdbApiKeywT),z: ) sort_keysindent separators)open CONFIG_FILEjsonloaddump)r key read_filecontent write_files r rzVirusdieLicense._write_keys $" # # +yi **G + + + + + + + + + + + + + + + #  $"C ( ( J I&                       s6::BB BN)__name__ __module__ __qualname__rr rr r rrs73K     r rFc tt5tddddS#1swxYwYdS)zWrite your migrations here.N)rFileNotFoundErrorrr migratordatabasefakekwargss r migrater)s # $ $''$$&&&''''''''''''''''''s!AA Ac dS)z$Write your rollback migrations here.Nr!r$s r rollbackr+%sDr )F) rlogging contextlibr getLoggerrloggerrr)r+r!r r r0s   8 $ $,''''      r defence360agent/migrations/__pycache__/052_whitelisted_crawlers.cpython-311.opt-1.pyc0000644000000000000000000000516000000000000025412 0ustar r_j,FddlZddlZejeZddZddZdS)NFc GddtjGfddtj}|||dS)zWrite your migrations here.cheZdZGddZejZejdZdS)#migrate..WhitelistedCrawlerceZdZdZdS)(migrate..WhitelistedCrawler.Metawhitelisted_crawlersN__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/052_whitelisted_crawlers.pyMetar s-HHHrrFnullN) r r r rpwPrimaryKeyFieldid TextField descriptionrrrWhitelistedCrawlerr s\ . . . . . . . . R  ! !"bl... rrceZdZGddZejZejdddZej dZ dS) )migrate..WhitelistedCrawlerDomainceZdZdZdS).migrate..WhitelistedCrawlerDomain.Metawhitelisted_crawler_domainsNr rrrrrs4HHHrrFCASCADEdomains)r on_delete related_namerN) r r r rrrrForeignKeyFieldcrawlerrdomain)rsrWhitelistedCrawlerDomainrs 5 5 5 5 5 5 5 5 R  ! !$"$ "     5)))rr&N)rModel create_model)migratordatabasefakekwargsr&rs @rmigrater- s/////RX/// * * * * * * *28 * * * ,--- 233333rc ||jd||jddS)z$Write your rollback migrations here.rrN) remove_modelorm)r)r*r+r,s rrollbackr1$s@ (,'=>??? (,'DEFFFFFr)F)loggingpeeweer getLoggerr loggerr-r1rrrr6sf  8 $ $44446GGGGGGrdefence360agent/migrations/__pycache__/052_whitelisted_crawlers.cpython-311.pyc0000644000000000000000000000516000000000000024453 0ustar r_j,FddlZddlZejeZddZddZdS)NFc GddtjGfddtj}|||dS)zWrite your migrations here.cheZdZGddZejZejdZdS)#migrate..WhitelistedCrawlerceZdZdZdS)(migrate..WhitelistedCrawler.Metawhitelisted_crawlersN__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/052_whitelisted_crawlers.pyMetar s-HHHrrFnullN) r r r rpwPrimaryKeyFieldid TextField descriptionrrrWhitelistedCrawlerr s\ . . . . . . . . R  ! !"bl... rrceZdZGddZejZejdddZej dZ dS) )migrate..WhitelistedCrawlerDomainceZdZdZdS).migrate..WhitelistedCrawlerDomain.Metawhitelisted_crawler_domainsNr rrrrrs4HHHrrFCASCADEdomains)r on_delete related_namerN) r r r rrrrForeignKeyFieldcrawlerrdomain)rsrWhitelistedCrawlerDomainrs 5 5 5 5 5 5 5 5 R  ! !$"$ "     5)))rr&N)rModel create_model)migratordatabasefakekwargsr&rs @rmigrater- s/////RX/// * * * * * * *28 * * * ,--- 233333rc ||jd||jddS)z$Write your rollback migrations here.rrN) remove_modelorm)r)r*r+r,s rrollbackr1$s@ (,'=>??? (,'DEFFFFFr)F)loggingpeeweer getLoggerr loggerr-r1rrrr6sf  8 $ $44446GGGGGGrdefence360agent/migrations/__pycache__/053_populate_whitelisted_crawlers.cpython-311.opt-1.pyc0000644000000000000000000000404100000000000027321 0ustar r_jKhddlZejeZdddgfdgdfddgfd d d gfgZdd ZddZdS)NzAGoogle (https://support.google.com/webmasters/answer/80553?hl=ru)z .google.comz.googlebot.comz[Yandex (https://yandex.ru/support/webmaster/robot-workings/check-yandex-robots.xml?lang=ru))z .yandex.ruz .yandex.comz .yandex.netzIBing (https://www.bing.com/webmaster/help/how-to-verify-bingbot-3905dc26)z.search.msn.comzGBaidu (http://help.baidu.com/question?prod_en=master&class=Baiduspider)z .baidu.comz .baidu.jpFc d|rdS|jd}|jd}|5tD][\}}||}|D]+} ||| ,\ ddddS#1swxYwYdS)zWrite your migrations here.Nwhitelisted_crawlerswhitelisted_crawler_domains) description)crawlerdomain)ormatomicDATAinsertexecute) migratordatabasefakekwargswcwcddescrdomains inserted_idds q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/053_populate_whitelisted_crawlers.pymigrater"s'  , -B ,4 5C   DD" D DNE7)))66>>@@K D D ;q 99AACCCC D DDDDDDDDDDDDDDDDDDDsA$B%%B),B)c dS)z$Write your rollback migrations here.N)rrrrs rrollbackr1sD)F)logging getLogger__name__loggerr rrrrrr"s  8 $ $ L ()  d 544  X   Q {# %8 D D D D      rdefence360agent/migrations/__pycache__/053_populate_whitelisted_crawlers.cpython-311.pyc0000644000000000000000000000404100000000000026362 0ustar r_jKhddlZejeZdddgfdgdfddgfd d d gfgZdd ZddZdS)NzAGoogle (https://support.google.com/webmasters/answer/80553?hl=ru)z .google.comz.googlebot.comz[Yandex (https://yandex.ru/support/webmaster/robot-workings/check-yandex-robots.xml?lang=ru))z .yandex.ruz .yandex.comz .yandex.netzIBing (https://www.bing.com/webmaster/help/how-to-verify-bingbot-3905dc26)z.search.msn.comzGBaidu (http://help.baidu.com/question?prod_en=master&class=Baiduspider)z .baidu.comz .baidu.jpFc d|rdS|jd}|jd}|5tD][\}}||}|D]+} ||| ,\ ddddS#1swxYwYdS)zWrite your migrations here.Nwhitelisted_crawlerswhitelisted_crawler_domains) description)crawlerdomain)ormatomicDATAinsertexecute) migratordatabasefakekwargswcwcddescrdomains inserted_idds q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/053_populate_whitelisted_crawlers.pymigrater"s'  , -B ,4 5C   DD" D DNE7)))66>>@@K D D ;q 99AACCCC D DDDDDDDDDDDDDDDDDDDsA$B%%B),B)c dS)z$Write your rollback migrations here.N)rrrrs rrollbackr1sD)F)logging getLogger__name__loggerr rrrrrr"s  8 $ $ L ()  d 544  X   Q {# %8 D D D D      rdefence360agent/migrations/__pycache__/054_add_malicious_and_added_date_fileds.cpython-311.opt-1.pyc0000644000000000000000000000302500000000000030246 0ustar r_j2ddlmZddlmZmZddZddZdS))time) BooleanField IntegerFieldFc |jd}||tdd|jd}||tdddS)N malware_hitsF)nulldefault) maliciousmalware_ignore_pathc8ttS)N)intrw/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/054_add_malicious_and_added_date_fileds.pyzmigrate..sCKKr) added_date)orm add_fieldsrrmigratordatabasefakekwargs MalwareHitsMalwareIgnorePaths rmigraters,~.K |FFF! %:; U4G4GHHHrc |jd}||d|jd}||ddS)Nrr r r)r remove_fieldsrs rrollbackrsO,~.K ; 444 %:; ,l;;;;;rN)F)rpeeweerrrrrrrr!sa--------    <<<<<zmigrate..sCKKr) added_date)orm add_fieldsrrmigratordatabasefakekwargs MalwareHitsMalwareIgnorePaths rmigraters,~.K |FFF! %:; U4G4GHHHrc |jd}||d|jd}||ddS)Nrr r r)r remove_fieldsrs rrollbackrsO,~.K ; 444 %:; ,l;;;;;rN)F)rpeeweerrrrrrrr!sa--------    <<<<<rso@@@@@@@@::<< G G G G G G G     rdefence360agent/migrations/__pycache__/055_migrate_move_to_quar_option.cpython-311.pyc0000644000000000000000000000242500000000000026030 0ustar r_j2@ddlmZmZdeddefdZdZdS)) ConfigFileIConfigF)fake config_filerc|rdS|x}sdS|di}|dd|dd|dd||dddS)NMALWARE_SCANNINGleave_suspiciousmax_days_in_quarantinemove_to_quarantineFT) overwritevalidate)config_to_dictgetpopdict_to_config)rr___configmalware_settingss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/055_migrate_move_to_quar_option.pymigraters !0022 2Fzz"4b99+T22214888-u555vFFFFFcdS)N)rrs rrollbackrsDrN) defence360agent.contracts.configrrrrrrrrso@@@@@@@@::<< G G G G G G G     rdefence360agent/migrations/__pycache__/056_populate_malicious_with_quarantined.cpython-311.opt-1.pyc0000644000000000000000000000100500000000000030505 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/056_populate_malicious_with_quarantined.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/056_populate_malicious_with_quarantined.cpython-311.pyc0000644000000000000000000000100500000000000027546 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/056_populate_malicious_with_quarantined.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/057_filename_is_blob.cpython-311.opt-1.pyc0000644000000000000000000000315200000000000024440 0ustar r_j#>ddlZejeZddZddZdS)NFc |d|d|d|ddS)z_ This migration os only for consistency, actually all works with CharField as well a CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" BLOB NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, "vendor" VARCHAR(255) NOT NULL, "malicious" INTEGER NOT NULL, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) z7INSERT INTO malware_hits_new SELECT * FROM malware_hitszDROP TABLE malware_hitsz3ALTER TABLE malware_hits_new RENAME TO malware_hitsN)sqlmigratordatabasefakekwargss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/057_filename_is_blob.pymigrater s`  LL  LLJKKK LL*+++ LLFGGGGGc dS)z$Write your rollback migrations here.Nrs r rollbackr!sDr )F)logging getLogger__name__loggerr rrr r rsX  8 $ $HHHH4      r defence360agent/migrations/__pycache__/057_filename_is_blob.cpython-311.pyc0000644000000000000000000000315200000000000023501 0ustar r_j#>ddlZejeZddZddZdS)NFc |d|d|d|ddS)z_ This migration os only for consistency, actually all works with CharField as well a CREATE TABLE "malware_hits_new" ( "id" INTEGER NOT NULL PRIMARY KEY, "scanid_id" VARCHAR(255) NOT NULL, "user" VARCHAR(255) NOT NULL, "orig_file" BLOB NOT NULL, "type" VARCHAR(255) NOT NULL, "restored" INTEGER NOT NULL, "mode" INTEGER, "vendor" VARCHAR(255) NOT NULL, "malicious" INTEGER NOT NULL, FOREIGN KEY ("scanid_id") REFERENCES "malware_scans" ("scanid")) z7INSERT INTO malware_hits_new SELECT * FROM malware_hitszDROP TABLE malware_hitsz3ALTER TABLE malware_hits_new RENAME TO malware_hitsN)sqlmigratordatabasefakekwargss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/057_filename_is_blob.pymigrater s`  LL  LLJKKK LL*+++ LLFGGGGGc dS)z$Write your rollback migrations here.Nrs r rollbackr!sDr )F)logging getLogger__name__loggerr rrr r rsX  8 $ $HHHH4      r defence360agent/migrations/__pycache__/058_convert_license_last_attempt.cpython-311.opt-1.pyc0000644000000000000000000000057400000000000027140 0ustar r_jddZdS)Fc dS)N)migratordatabasefakekwargss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/058_convert_license_last_attempt.pymigrater sDN)F)r rr rr s#      r defence360agent/migrations/__pycache__/058_convert_license_last_attempt.cpython-311.pyc0000644000000000000000000000057400000000000026201 0ustar r_jddZdS)Fc dS)N)migratordatabasefakekwargss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/058_convert_license_last_attempt.pymigrater sDN)F)r rr rr s#      r defence360agent/migrations/__pycache__/059_scans_error_field.cpython-311.opt-1.pyc0000644000000000000000000000164400000000000024660 0ustar r_j|ddlZddZddZdS)NFc v|jd}||tjdddS)N malware_scansT)defaultnull)error)orm add_fieldspw TextFieldmigratordatabasefakekwargs MalwareScanss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/059_scans_error_field.pymigratersI<0L BLDAAAc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s*<0L <11111r)F)peeweer rrrrrsC222222rdefence360agent/migrations/__pycache__/059_scans_error_field.cpython-311.pyc0000644000000000000000000000164400000000000023721 0ustar r_j|ddlZddZddZdS)NFc v|jd}||tjdddS)N malware_scansT)defaultnull)error)orm add_fieldspw TextFieldmigratordatabasefakekwargs MalwareScanss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/059_scans_error_field.pymigratersI<0L BLDAAAc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s*<0L <11111r)F)peeweer rrrrrsC222222rdefence360agent/migrations/__pycache__/061_migrate_backup_system_conf.cpython-311.opt-1.pyc0000644000000000000000000000406200000000000026551 0ustar r_jEdZddlZddlmZddlmZmZmZmZddl m Z e j dedfdedeefd Z e j d d Z dS) zT Migrate backup config from user oriented config file to the separate internal file N)Optional) BackupConfigIConfig IConfigFile LocalConfig)antivirus_modeF config_filebackup_config_filec |rdS|t}|x}sdStj|jrdS|di}d|dd|dddi}||dd||dddS) NBACKUP_RESTORE BACKUP_SYSTEMenabledF backup_system)rrT) overwritevalidate)rconfig_to_dictospathexistsgetpopdict_to_config) migratordatabasefaker r kwargs config_frombackup_conf_current config_tos n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/061_migrate_backup_system_conf.pymigrater!s !)^^&5577 7K w~~(-..%//*:B??*..y%@@044_dKK  I %%TE&{dUKKKKKc dS)z$Write your rollback migrations here.N)rrrrs r rollbackr%2s  Dr")F)__doc__rtypingr defence360agent.contracts.configrrrrdefence360agent.utilsrskipr!r%r$r"r r+s  100000 &;==04 LL L !- LLLL@      r"defence360agent/migrations/__pycache__/061_migrate_backup_system_conf.cpython-311.pyc0000644000000000000000000000406200000000000025612 0ustar r_jEdZddlZddlmZddlmZmZmZmZddl m Z e j dedfdedeefd Z e j d d Z dS) zT Migrate backup config from user oriented config file to the separate internal file N)Optional) BackupConfigIConfig IConfigFile LocalConfig)antivirus_modeF config_filebackup_config_filec |rdS|t}|x}sdStj|jrdS|di}d|dd|dddi}||dd||dddS) NBACKUP_RESTORE BACKUP_SYSTEMenabledF backup_system)rrT) overwritevalidate)rconfig_to_dictospathexistsgetpopdict_to_config) migratordatabasefaker r kwargs config_frombackup_conf_current config_tos n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/061_migrate_backup_system_conf.pymigrater!s !)^^&5577 7K w~~(-..%//*:B??*..y%@@044_dKK  I %%TE&{dUKKKKKc dS)z$Write your rollback migrations here.N)rrrrs r rollbackr%2s  Dr")F)__doc__rtypingr defence360agent.contracts.configrrrrdefence360agent.utilsrskipr!r%r$r"r r+s  100000 &;==04 LL L !- LLLL@      r"defence360agent/migrations/__pycache__/062_drop_malware_extra_data.cpython-311.opt-1.pyc0000644000000000000000000000135200000000000026033 0ustar r_j5ddZddZdS)Fc J|jd}||dS)zWrite your migrations here.malware_hit_extrasN)orm remove_model)migratordatabasefakekwargsMalwareExtraDatas k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_drop_malware_extra_data.pymigrater s+|$89 *+++++c dS)z$Write your rollback migrations here.N)rrrr s r rollbackrsDr N)F)r rrr r rs7,,,,       r defence360agent/migrations/__pycache__/062_drop_malware_extra_data.cpython-311.pyc0000644000000000000000000000135200000000000025074 0ustar r_j5ddZddZdS)Fc J|jd}||dS)zWrite your migrations here.malware_hit_extrasN)orm remove_model)migratordatabasefakekwargsMalwareExtraDatas k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_drop_malware_extra_data.pymigrater s+|$89 *+++++c dS)z$Write your rollback migrations here.N)rrrr s r rollbackrsDr N)F)r rrr r rs7,,,,       r defence360agent/migrations/__pycache__/062_fix_null_expiration.cpython-311.opt-1.pyc0000644000000000000000000000205500000000000025246 0ustar r_jdZddZddZdS)z> Fix IPs that were added with NULL expiration to the WB lists Fc |jd}|d|jddg|jzdS)Niplist) expirationWHITEBLACK)ormupdatewherelistnamein_ris_nullexecute)migratordatabasefakekwargs IPListModels g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_fix_null_expiration.pymigraterss,x(K!$$**   ! !7G"4 5 5  ! ) ) + + -giiiiic dS)N)rrrrs rrollbackrsDrN)F)__doc__rrrrrrsA       rdefence360agent/migrations/__pycache__/062_fix_null_expiration.cpython-311.pyc0000644000000000000000000000205500000000000024307 0ustar r_jdZddZddZdS)z> Fix IPs that were added with NULL expiration to the WB lists Fc |jd}|d|jddg|jzdS)Niplist) expirationWHITEBLACK)ormupdatewherelistnamein_ris_nullexecute)migratordatabasefakekwargs IPListModels g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/062_fix_null_expiration.pymigraterss,x(K!$$**   ! !7G"4 5 5  ! ) ) + + -giiiiic dS)N)rrrrs rrollbackrsDrN)F)__doc__rrrrrrsA       r././@LongLink0000644000000000000000000000015500000000000007774 Lustar defence360agent/migrations/__pycache__/063_fix_graylist_doslist_expiration_discrepancy.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/063_fix_graylist_doslist_expiration_discrepancy.cpython-311.o0000644000000000000000000000165300000000000030767 0ustar r_jaddZddZdS)Fc |jd}||j|jdk|j|jkzdS)Niplist) expirationGRAY)ormupdatedos_expirationwherelistnamerexecute)migratordatabasefakekwargs IPListModels /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.pymigraterse,x(K+"<==CC   '  !K$> > @giiiiic dS)N)r r rrs rrollbackr sDrN)F)rrrrrrs7      r././@LongLink0000644000000000000000000000014700000000000007775 Lustar defence360agent/migrations/__pycache__/063_fix_graylist_doslist_expiration_discrepancy.cpython-311.pycdefence360agent/migrations/__pycache__/063_fix_graylist_doslist_expiration_discrepancy.cpython-311.p0000644000000000000000000000165300000000000030770 0ustar r_jaddZddZdS)Fc |jd}||j|jdk|j|jkzdS)Niplist) expirationGRAY)ormupdatedos_expirationwherelistnamerexecute)migratordatabasefakekwargs IPListModels /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.pymigraterse,x(K+"<==CC   '  !K$> > @giiiiic dS)N)r r rrs rrollbackr sDrN)F)rrrrrrs7      rdefence360agent/migrations/__pycache__/064_chmod_i360deploy_log.cpython-311.opt-1.pyc0000644000000000000000000000170300000000000025076 0ustar r_j .ddlmZddlZdZddZddZdS))suppressNz/var/log/i360deploy.logFc tt5tjtdddddS#1swxYwYdS)Ni)rFileNotFoundErroroschmodI360DEPLOY_LOGmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/064_chmod_i360deploy_log.pymigraters # $ $(( '''((((((((((((((((((s=AAc dS)Nr s rrollbackr sD)F) contextlibrrrrrrrrrsW *((((       rdefence360agent/migrations/__pycache__/064_chmod_i360deploy_log.cpython-311.pyc0000644000000000000000000000170300000000000024137 0ustar r_j .ddlmZddlZdZddZddZdS))suppressNz/var/log/i360deploy.logFc tt5tjtdddddS#1swxYwYdS)Ni)rFileNotFoundErroroschmodI360DEPLOY_LOGmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/064_chmod_i360deploy_log.pymigraters # $ $(( '''((((((((((((((((((s=AAc dS)Nr s rrollbackr sD)F) contextlibrrrrrrrrrsW *((((       rdefence360agent/migrations/__pycache__/065_remove_capture_csf_lock_from_config.cpython-311.opt-1.pyc0000644000000000000000000000242600000000000030424 0ustar r_jaRddlZddlZddlmZejeZddZddZdS)N) LocalConfigFc |rdSt}tj|jsdS|}d|vr/|d||dddSdS)NCSF_COOPERATIONTF) overwritevalidate)rospathexistsconfig_to_dictpopdict_to_config)migratordatabasefakekwargs local_configconfigs w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/065_remove_capture_csf_lock_from_config.pymigrater s ==L 7>>,+ , ,  ( ( * *FF"" $%%%##FdU#KKKKK#"c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F) loggingr defence360agent.contracts.configr getLogger__name__loggerrrrrrrsq 888888  8 $ $ L L L L      rdefence360agent/migrations/__pycache__/065_remove_capture_csf_lock_from_config.cpython-311.pyc0000644000000000000000000000242600000000000027465 0ustar r_jaRddlZddlZddlmZejeZddZddZdS)N) LocalConfigFc |rdSt}tj|jsdS|}d|vr/|d||dddSdS)NCSF_COOPERATIONTF) overwritevalidate)rospathexistsconfig_to_dictpopdict_to_config)migratordatabasefakekwargs local_configconfigs w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/065_remove_capture_csf_lock_from_config.pymigrater s ==L 7>>,+ , ,  ( ( * *FF"" $%%%##FdU#KKKKK#"c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F) loggingr defence360agent.contracts.configr getLogger__name__loggerrrrrrrsq 888888  8 $ $ L L L L      rdefence360agent/migrations/__pycache__/066_eula_table.cpython-311.opt-1.pyc0000644000000000000000000000261600000000000023270 0ustar r_jDddlZGddejZddZddZdS)NcneZdZGddZejdZejddZdS)EulaceZdZdZdS) Eula.MetaeulaN)__name__ __module__ __qualname__db_table^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/066_eula_table.pyMetarsr rT) primary_keyN)nulldefault) rr r rpw DateFieldupdated IntegerFieldacceptedr r rrrsbblt,,,GrD$777HHHr rFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s $r c J|jd}||dS)Nr)orm remove_model)rrrrrs rrollbackr"s( < D $r )F)peeweerModelrrr"r r rr%so8888828888          r defence360agent/migrations/__pycache__/066_eula_table.cpython-311.pyc0000644000000000000000000000261600000000000022331 0ustar r_jDddlZGddejZddZddZdS)NcneZdZGddZejdZejddZdS)EulaceZdZdZdS) Eula.MetaeulaN)__name__ __module__ __qualname__db_table^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/066_eula_table.pyMetarsr rT) primary_keyN)nulldefault) rr r rpw DateFieldupdated IntegerFieldacceptedr r rrrsbblt,,,GrD$777HHHr rFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s $r c J|jd}||dS)Nr)orm remove_model)rrrrrs rrollbackr"s( < D $r )F)peeweerModelrrr"r r rr%so8888828888          r defence360agent/migrations/__pycache__/067_drop_fields_from_modsec_conf.cpython-311.opt-1.pyc0000644000000000000000000000231300000000000027042 0ustar r_j<@ddlmZmZdefdefdZddZdS))IConfig LocalConfigF config_filec |rdS|}|sdS|di}|dd|dd||dddS)NMOD_SEC was_installed OWASP_deletedFT)validate overwrite)config_to_dict setdefaultpopdict_to_config)migratordatabasefakerkwargsconfmod_sec_settingss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/067_drop_fields_from_modsec_conf.pymigraters   % % ' 'D y"55$///$///tetDDDDDc dS)N)rrrrs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrrsrAAAAAAAA &;== EE EEEE*      rdefence360agent/migrations/__pycache__/067_drop_fields_from_modsec_conf.cpython-311.pyc0000644000000000000000000000231300000000000026103 0ustar r_j<@ddlmZmZdefdefdZddZdS))IConfig LocalConfigF config_filec |rdS|}|sdS|di}|dd|dd||dddS)NMOD_SEC was_installed OWASP_deletedFT)validate overwrite)config_to_dict setdefaultpopdict_to_config)migratordatabasefakerkwargsconfmod_sec_settingss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/067_drop_fields_from_modsec_conf.pymigraters   % % ' 'D y"55$///$///tetDDDDDc dS)N)rrrrs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrrsrAAAAAAAA &;== EE EEEE*      r././@LongLink0000644000000000000000000000015100000000000007770 Lustar defence360agent/migrations/__pycache__/068_remove_rules_check_interval_from_config.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/068_remove_rules_check_interval_from_config.cpython-311.opt-10000644000000000000000000000223700000000000030522 0ustar r_j?JddlZddlmZejeZddZddZdS)N) ConfigFileFc |rdSt}|}|sdSd|vr/|d||dddSdS)NIPTABLES_RULE_CHECKTF) overwritevalidate)rconfig_to_dictpopdict_to_config)migratordatabasefakekwargs config_fileconfigs {/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/068_remove_rules_check_interval_from_config.pymigraters} ,,K  ' ' ) )F && ()))""6TE"JJJJJ'&c dS)z$Write your rollback migrations here.N)r r r rs rrollbackrsDr)F)logging defence360agent.contracts.configr getLogger__name__loggerrrrrrrsh777777  8 $ $ K K K K      rdefence360agent/migrations/__pycache__/068_remove_rules_check_interval_from_config.cpython-311.pyc0000644000000000000000000000223700000000000030355 0ustar r_j?JddlZddlmZejeZddZddZdS)N) ConfigFileFc |rdSt}|}|sdSd|vr/|d||dddSdS)NIPTABLES_RULE_CHECKTF) overwritevalidate)rconfig_to_dictpopdict_to_config)migratordatabasefakekwargs config_fileconfigs {/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/068_remove_rules_check_interval_from_config.pymigraters} ,,K  ' ' ) )F && ()))""6TE"JJJJJ'&c dS)z$Write your rollback migrations here.N)r r r rs rrollbackrsDr)F)logging defence360agent.contracts.configr getLogger__name__loggerrrrrrrsh777777  8 $ $ K K K K      rdefence360agent/migrations/__pycache__/069_incidents_domain_field.cpython-311.opt-1.pyc0000644000000000000000000000163000000000000025643 0ustar r_jVddlZddZddZdS)NFc v|jd}||tjdddS)NincidentT)defaultnull)domain)orm add_fieldspw TextFieldmigratordatabasefakekwargsIncidents j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/069_incidents_domain_field.pymigraters<|J'H d)N)N)NOOOOOc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s*|J'H 8X.....r)F)peeweer rrrrrsGPPPP //////rdefence360agent/migrations/__pycache__/069_incidents_domain_field.cpython-311.pyc0000644000000000000000000000163000000000000024704 0ustar r_jVddlZddZddZdS)NFc v|jd}||tjdddS)NincidentT)defaultnull)domain)orm add_fieldspw TextFieldmigratordatabasefakekwargsIncidents j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/069_incidents_domain_field.pymigraters<|J'H d)N)N)NOOOOOc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s*|J'H 8X.....r)F)peeweer rrrrrsGPPPP //////rdefence360agent/migrations/__pycache__/070_modsec_incident_names.cpython-311.opt-1.pyc0000644000000000000000000000605600000000000025502 0ustar r_jD(ddlmZdZddZddZdS)) TemporaryFilec<|dddS)N||maxsplitr)split) descriptions i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/070_modsec_incident_names.py extract_namer s   TA  . .q 11Fc  |jd fd}td5}|D];\}}|d|t |<|d|5|D]}|dd \}} |  j t|k  d d d n #1swxYwYd d d d S#1swxYwYd S) z This migration extracts incident name from whole mod_security message Centos6 version of sqlite does not have instr(), using slow python-based way incidentc3RK jjjdkjdEd{VdS#t$rYdSwxYw)Nmodsecr) selectidr whereplugincontainstuplesiterator RuntimeError)rsr select_incidentsz!migrate..select_incidentss  X-ABBx(233x+44T::;;              FF sBB B&%B&zw+)modez{},{} r,rr)nameN)ormrwriteformatr seekatomicr updatestriprrintexecute) migratordatabasefakekwargsrfid_desclinerrs @r migrater/s |J'H      D ! ! ! Q))++ ? ?IC GGI$$S,t*<*<== > > > > q __       JJsQJ77 TTZZ\\2288K3s88+'))))                                   s7A0D<BD$ D<$D( (D<+D( ,D<<EEc dS)z$Write your rollback migrations here.N)r'r(r)r*s r rollbackr2)sDr N)F)tempfilerr r/r2r1r r r4sY""""""222B      r defence360agent/migrations/__pycache__/070_modsec_incident_names.cpython-311.pyc0000644000000000000000000000605600000000000024543 0ustar r_jD(ddlmZdZddZddZdS)) TemporaryFilec<|dddS)N||maxsplitr)split) descriptions i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/070_modsec_incident_names.py extract_namer s   TA  . .q 11Fc  |jd fd}td5}|D];\}}|d|t |<|d|5|D]}|dd \}} |  j t|k  d d d n #1swxYwYd d d d S#1swxYwYd S) z This migration extracts incident name from whole mod_security message Centos6 version of sqlite does not have instr(), using slow python-based way incidentc3RK jjjdkjdEd{VdS#t$rYdSwxYw)Nmodsecr) selectidr whereplugincontainstuplesiterator RuntimeError)rsr select_incidentsz!migrate..select_incidentss  X-ABBx(233x+44T::;;              FF sBB B&%B&zw+)modez{},{} r,rr)nameN)ormrwriteformatr seekatomicr updatestriprrintexecute) migratordatabasefakekwargsrfid_desclinerrs @r migrater/s |J'H      D ! ! ! Q))++ ? ?IC GGI$$S,t*<*<== > > > > q __       JJsQJ77 TTZZ\\2288K3s88+'))))                                   s7A0D<BD$ D<$D( (D<+D( ,D<<EEc dS)z$Write your rollback migrations here.N)r'r(r)r*s r rollbackr2)sDr N)F)tempfilerr r/r2r1r r r4sY""""""222B      r defence360agent/migrations/__pycache__/071_malware_hits_hash_size_fields.cpython-311.opt-1.pyc0000644000000000000000000000222200000000000027222 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdtjddS)zWrite your migrations here. malware_hitsT)null)sizehashN)orm add_fieldspw CharFieldmigratordatabasefakekwargs MalwareHitss q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/071_malware_hits_hash_size_fields.pymigratersV,~.K ",D111 $8O8O8Oc N|jd}||dddS)z$Write your rollback migrations here.rrrN)r remove_fieldsr s rrollbackrs,,~.K ;77777r)F)loggingpeeweer getLogger__name__loggerrrrrrs^  8 $ $888888rdefence360agent/migrations/__pycache__/071_malware_hits_hash_size_fields.cpython-311.pyc0000644000000000000000000000222200000000000026263 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdtjddS)zWrite your migrations here. malware_hitsT)null)sizehashN)orm add_fieldspw CharFieldmigratordatabasefakekwargs MalwareHitss q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/071_malware_hits_hash_size_fields.pymigratersV,~.K ",D111 $8O8O8Oc N|jd}||dddS)z$Write your rollback migrations here.rrrN)r remove_fieldsr s rrollbackrs,,~.K ;77777r)F)loggingpeeweer getLogger__name__loggerrrrrrs^  8 $ $888888rdefence360agent/migrations/__pycache__/072_add_malware_history_table.cpython-311.opt-1.pyc0000644000000000000000000000375400000000000026364 0ustar r_j\ddlmZddlZddlmZGddejZd dZd dZdS) )timeN) FilenameFieldceZdZGddZedZejdZejdZ ejdZ ejdZ ej ddZ dS) MalwareHistoryceZdZdZdS)MalwareHistory.Metamalware_historyN)__name__ __module__ __qualname__db_tablem/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_add_malware_history_table.pyMetar s$rrF)nullTc8ttSN)intrrrrzMalwareHistory.ss466{{r)rdefaultN)r r r rrpathpw CharFieldevent initiatorcause file_owner IntegerFieldctimerrrrrs%%%%%%%% =e $ $ $D BLe $ $ $E %(((I BLe $ $ $E4(((J BO/B/B C C CEEErrFc :|tdSr) create_modelr)migratordatabasefakekwargss rmigrater's .)))))rc J|jd}||dS)Nr )orm remove_model)r#r$r%r&rs rrollbackr+s)\"34N .)))))r)F) rpeeweer$defence360agent.model.simplificationrModelrr'r+rrrr/s>>>>>> D D D D DRX D D D**********rdefence360agent/migrations/__pycache__/072_add_malware_history_table.cpython-311.pyc0000644000000000000000000000375400000000000025425 0ustar r_j\ddlmZddlZddlmZGddejZd dZd dZdS) )timeN) FilenameFieldceZdZGddZedZejdZejdZ ejdZ ejdZ ej ddZ dS) MalwareHistoryceZdZdZdS)MalwareHistory.Metamalware_historyN)__name__ __module__ __qualname__db_tablem/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_add_malware_history_table.pyMetar s$rrF)nullTc8ttSN)intrrrrzMalwareHistory.ss466{{r)rdefaultN)r r r rrpathpw CharFieldevent initiatorcause file_owner IntegerFieldctimerrrrrs%%%%%%%% =e $ $ $D BLe $ $ $E %(((I BLe $ $ $E4(((J BO/B/B C C CEEErrFc :|tdSr) create_modelr)migratordatabasefakekwargss rmigrater's .)))))rc J|jd}||dS)Nr )orm remove_model)r#r$r%r&rs rrollbackr+s)\"34N .)))))r)F) rpeeweer$defence360agent.model.simplificationrModelrr'r+rrrr/s>>>>>> D D D D DRX D D D**********rdefence360agent/migrations/__pycache__/072_captcha_stat.cpython-311.opt-1.pyc0000644000000000000000000000502500000000000023623 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NceZdZdZejddZejdddZejdZGdd Z d S) Countryz( Contains country code and name TF) primary_keynull) max_lengthuniquerrceZdZdZdS) Country.MetacountryN)__name__ __module__ __qualname__db_table`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_captcha_stat.pyMetar srrN) rrr__doc__pw CharFieldidcodenamerrrrrrs $U 3 3 3B 2<1T > > >D 2r8s     bh    ( ( ( ( ("( ( ( ( ''''rdefence360agent/migrations/__pycache__/072_captcha_stat.cpython-311.pyc0000644000000000000000000000502500000000000022664 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NceZdZdZejddZejdddZejdZGdd Z d S) Countryz( Contains country code and name TF) primary_keynull) max_lengthuniquerrceZdZdZdS) Country.MetacountryN)__name__ __module__ __qualname__db_table`/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_captcha_stat.pyMetar srrN) rrr__doc__pw CharFieldidcodenamerrrrrrs $U 3 3 3B 2<1T > > >D 2r8s     bh    ( ( ( ( ("( ( ( ( ''''rdefence360agent/migrations/__pycache__/072_extend_last_synclist.cpython-311.opt-1.pyc0000644000000000000000000000221100000000000025421 0ustar r_jddZddZdS)Fc |d|d|d|ddS)z4Recreating DB in order to make `name` as primary keyz~ CREATE TABLE "last_synclist_new" ( "timestamp" REAL, "name" VARCHAR(255) NOT NULL PRIMARY KEY )zWINSERT INTO last_synclist_new SELECT timestamp, "ip" AS name FROM last_synclist LIMIT 1zDROP TABLE last_synclistz5ALTER TABLE last_synclist_new RENAME TO last_synclistN)sqlmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_extend_last_synclist.pymigrater si LL  LL D LL+,,, LLHIIIIIc dS)z$Write your rollback migrations here.Nrs r rollbackrsr N)F)r rr r r rs;JJJJ"//////r defence360agent/migrations/__pycache__/072_extend_last_synclist.cpython-311.pyc0000644000000000000000000000221100000000000024462 0ustar r_jddZddZdS)Fc |d|d|d|ddS)z4Recreating DB in order to make `name` as primary keyz~ CREATE TABLE "last_synclist_new" ( "timestamp" REAL, "name" VARCHAR(255) NOT NULL PRIMARY KEY )zWINSERT INTO last_synclist_new SELECT timestamp, "ip" AS name FROM last_synclist LIMIT 1zDROP TABLE last_synclistz5ALTER TABLE last_synclist_new RENAME TO last_synclistN)sqlmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/072_extend_last_synclist.pymigrater si LL  LL D LL+,,, LLHIIIIIc dS)z$Write your rollback migrations here.Nrs r rollbackrsr N)F)r rr r r rs;JJJJ"//////r defence360agent/migrations/__pycache__/073_drop_dos_expiration.cpython-311.opt-1.pyc0000644000000000000000000000231700000000000025242 0ustar r_jWFddlZddlZejeZddZddZdS)NFc |jd}||tjdd|d||ddS)zWrite your migrations here.iplistF)nulldefault) no_captchazGUPDATE iplist SET no_captcha=1 WHERE listname='GRAY' AND dos_expirationdos_expirationN)orm add_fieldspw BooleanFieldsql remove_fields)migratordatabasefakekwargsIPLists g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/073_drop_dos_expiration.pymigraters| \( #F ?u=== LL 3 6#344444c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F)loggingpeeweer getLogger__name__loggerrrrrrrs^  8 $ $ 5 5 5 5      rdefence360agent/migrations/__pycache__/073_drop_dos_expiration.cpython-311.pyc0000644000000000000000000000231700000000000024303 0ustar r_jWFddlZddlZejeZddZddZdS)NFc |jd}||tjdd|d||ddS)zWrite your migrations here.iplistF)nulldefault) no_captchazGUPDATE iplist SET no_captcha=1 WHERE listname='GRAY' AND dos_expirationdos_expirationN)orm add_fieldspw BooleanFieldsql remove_fields)migratordatabasefakekwargsIPLists g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/073_drop_dos_expiration.pymigraters| \( #F ?u=== LL 3 6#344444c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F)loggingpeeweer getLogger__name__loggerrrrrrrs^  8 $ $ 5 5 5 5      rdefence360agent/migrations/__pycache__/074_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000557400000000000023145 0ustar r_j RddlZddlmZddlZejeZddZddZdS)N)timeFc |jdGfddtj}||dS)zWrite your migrations here.countryc2eZdZejdZejdejdgZejddZ ejdZ ejdd Z ejdZ ejdZ ejdZejdd ZejdZejdd ZejdZejdZejdZGd d Zd S) migrate..IPListNewF)nullz$listname in ('WHITE','BLACK','GRAY'))r constraintsrT)defaultrc8ttS)N)intr]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/074_ip_as_int.pyz#migrate..IPListNew.ss466{{r)rr c6eZdZdZejdddZdS)migrate..IPListNew.Meta iplist_newnetwork_addressnetmaskversionN)__name__ __module__ __qualname__db_tablepw CompositeKey primary_keyr rrMetar)s-#H)"/!9iKKKrrN)rrrr CharFieldipChecklistname IntegerField expiration imported_fromctimedeepcommentForeignKeyFieldr BooleanField no_captcha full_accessauto_whitelistedrrrr)Countrysr IPListNewrsR\u % % %2<!"HIIJ   %R_D   % $/// 22   rD)))",D)))$"$W4888$R_%??? %bo4000 *2?eDDD)"/u555!"/u---!"/u---          rr/N)ormrModel create_model)migratordatabasefakekwargsr/r.s @rmigrater7 sdl9%GBH@ )$$$$$rc dS)z$Write your rollback migrations here.Nr )r3r4r5r6s rrollbackr92sDr)F) loggingrpeeweer getLoggerrloggerr7r9r rrr>ss  8 $ $%%%%%%%%P      rdefence360agent/migrations/__pycache__/074_ip_as_int.cpython-311.pyc0000644000000000000000000000557400000000000022206 0ustar r_j RddlZddlmZddlZejeZddZddZdS)N)timeFc |jdGfddtj}||dS)zWrite your migrations here.countryc2eZdZejdZejdejdgZejddZ ejdZ ejdd Z ejdZ ejdZ ejdZejdd ZejdZejdd ZejdZejdZejdZGd d Zd S) migrate..IPListNewF)nullz$listname in ('WHITE','BLACK','GRAY'))r constraintsrT)defaultrc8ttS)N)intr]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/074_ip_as_int.pyz#migrate..IPListNew.ss466{{r)rr c6eZdZdZejdddZdS)migrate..IPListNew.Meta iplist_newnetwork_addressnetmaskversionN)__name__ __module__ __qualname__db_tablepw CompositeKey primary_keyr rrMetar)s-#H)"/!9iKKKrrN)rrrr CharFieldipChecklistname IntegerField expiration imported_fromctimedeepcommentForeignKeyFieldr BooleanField no_captcha full_accessauto_whitelistedrrrr)Countrysr IPListNewrsR\u % % %2<!"HIIJ   %R_D   % $/// 22   rD)))",D)))$"$W4888$R_%??? %bo4000 *2?eDDD)"/u555!"/u---!"/u---          rr/N)ormrModel create_model)migratordatabasefakekwargsr/r.s @rmigrater7 sdl9%GBH@ )$$$$$rc dS)z$Write your rollback migrations here.Nr )r3r4r5r6s rrollbackr92sDr)F) loggingrpeeweer getLoggerrloggerr7r9r rrr>ss  8 $ $%%%%%%%%P      rdefence360agent/migrations/__pycache__/075_ips_as_int.cpython-311.opt-1.pyc0000644000000000000000000000626300000000000023325 0ustar r_jNddlZddlZddlZejeZddZddZdS)NFc " |jd}|jd fd} ddlm}|5|D]} t j|d}n#t $rY*wxYw||\} } } || | | d || #tj $r%} t d| Yd } ~ d } ~ wwxYw d d d n #1swxYwYn#t$rYnwxYw|d |d |d |d |dd S)zWrite your migrations here. iplist_newiplistc3K Ed{VdS#t$rYdSwxYw)N)selectdictsiterator RuntimeError)IPLists^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/075_ips_as_int.py iplist_selectzmigrate..iplist_selectsp }}V,,2244==?? ? ? ? ? ? ? ? ? ?    FF s?A AAr)pack_ip_networkip)network_addressnetmaskversionzError inserting IP: %sNzDROP TABLE iplistz'ALTER TABLE iplist_new RENAME TO iplistz7CREATE INDEX "iplist_listname" ON "iplist" ("listname")z;CREATE INDEX "iplist_expiration" ON "iplist" ("expiration")z+CREATE INDEX "iplist_ip" ON "iplist" ("ip"))ormim360.utils.netratomic ipaddress ip_network ValueErrorupdateinsertexecutepwIntegrityErrorloggerwarning ImportErrorsql)migratordatabasefakekwargs IPListNewr rip_objrnetmaskrer s @r migrater+ sZ \*I \( #F@333333__   @ @'-// @ @"-fTl;;BB!H&5_R%8%8"T7 +.#'#*@$$V,,446666(@@@NN#;Q????????@% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @      2 LL$%%% LL:;;; LLJKKK LLNOOO LL>?????spD D  A%$D % A2/D 1A22+D 'CD C:C50D 5C::D  DD D#"D#c dS)z$Write your rollback migrations here.N)r"r#r$r%s r rollbackr.8sD)F) loggingpeeweerr getLogger__name__rr+r.r-r/r r4sq  8 $ $+@+@+@+@\      r/defence360agent/migrations/__pycache__/075_ips_as_int.cpython-311.pyc0000644000000000000000000000626300000000000022366 0ustar r_jNddlZddlZddlZejeZddZddZdS)NFc " |jd}|jd fd} ddlm}|5|D]} t j|d}n#t $rY*wxYw||\} } } || | | d || #tj $r%} t d| Yd } ~ d } ~ wwxYw d d d n #1swxYwYn#t$rYnwxYw|d |d |d |d |dd S)zWrite your migrations here. iplist_newiplistc3K Ed{VdS#t$rYdSwxYw)N)selectdictsiterator RuntimeError)IPLists^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/075_ips_as_int.py iplist_selectzmigrate..iplist_selectsp }}V,,2244==?? ? ? ? ? ? ? ? ? ?    FF s?A AAr)pack_ip_networkip)network_addressnetmaskversionzError inserting IP: %sNzDROP TABLE iplistz'ALTER TABLE iplist_new RENAME TO iplistz7CREATE INDEX "iplist_listname" ON "iplist" ("listname")z;CREATE INDEX "iplist_expiration" ON "iplist" ("expiration")z+CREATE INDEX "iplist_ip" ON "iplist" ("ip"))ormim360.utils.netratomic ipaddress ip_network ValueErrorupdateinsertexecutepwIntegrityErrorloggerwarning ImportErrorsql)migratordatabasefakekwargs IPListNewr rip_objrnetmaskrer s @r migrater+ sZ \*I \( #F@333333__   @ @'-// @ @"-fTl;;BB!H&5_R%8%8"T7 +.#'#*@$$V,,446666(@@@NN#;Q????????@% @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @      2 LL$%%% LL:;;; LLJKKK LLNOOO LL>?????spD D  A%$D % A2/D 1A22+D 'CD C:C50D 5C::D  DD D#"D#c dS)z$Write your rollback migrations here.N)r"r#r$r%s r rollbackr.8sD)F) loggingpeeweerr getLogger__name__rr+r.r-r/r r4sq  8 $ $+@+@+@+@\      r/defence360agent/migrations/__pycache__/076_hash_model.cpython-311.opt-1.pyc0000644000000000000000000000074500000000000023300 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/076_hash_model.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/076_hash_model.cpython-311.pyc0000644000000000000000000000074500000000000022341 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/076_hash_model.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/077_alter_malware_scan.cpython-311.opt-1.pyc0000644000000000000000000000275100000000000025020 0ustar r_j."dZddlZddZddZdS)z[ Altering MalwareScan.type in order to add ability to support 'malware-response' scan type NFc z|jd}|d|jdk||t jdd||t jd t jd g dS) N malware_scansrealtime)typeinotifyT)nulldefault)pathFz5type in ('on-demand', 'realtime', 'malware-response'))r constraints) ormupdatewhererexecute change_fieldspw CharFieldCheck)migratordatabasefakekwargs MalwareScans f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/077_alter_malware_scan.pymigraters,/KJ''--I% giii ",D"===  \K        c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F)__doc__peeweerrrrrrr"sO.      rdefence360agent/migrations/__pycache__/077_alter_malware_scan.cpython-311.pyc0000644000000000000000000000275100000000000024061 0ustar r_j."dZddlZddZddZdS)z[ Altering MalwareScan.type in order to add ability to support 'malware-response' scan type NFc z|jd}|d|jdk||t jdd||t jd t jd g dS) N malware_scansrealtime)typeinotifyT)nulldefault)pathFz5type in ('on-demand', 'realtime', 'malware-response'))r constraints) ormupdatewhererexecute change_fieldspw CharFieldCheck)migratordatabasefakekwargs MalwareScans f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/077_alter_malware_scan.pymigraters,/KJ''--I% giii ",D"===  \K        c dS)z$Write your rollback migrations here.N)rrrrs rrollbackrsDr)F)__doc__peeweerrrrrrr"sO.      rdefence360agent/migrations/__pycache__/078_fix_signatures_permissions.cpython-311.opt-1.pyc0000644000000000000000000000121300000000000026653 0ustar r_j dZddZddZdS)zRRemoved, as DEF-11611 will fix folder creations so it will not be needed anymore. Fc dS)Nmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/078_fix_signatures_permissions.pymigrater sDc dS)z$Write your rollback migrations here.Nrrs r rollbackr sDr N)F)__doc__r r rr r rsA           r defence360agent/migrations/__pycache__/078_fix_signatures_permissions.cpython-311.pyc0000644000000000000000000000121300000000000025714 0ustar r_j dZddZddZdS)zRRemoved, as DEF-11611 will fix folder creations so it will not be needed anymore. Fc dS)Nmigratordatabasefakekwargss n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/078_fix_signatures_permissions.pymigrater sDc dS)z$Write your rollback migrations here.Nrrs r rollbackr sDr N)F)__doc__r r rr r rsA           r defence360agent/migrations/__pycache__/079_add_uid_gid_fields.cpython-311.opt-1.pyc0000644000000000000000000000211000000000000024726 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdtjddS)N malware_hitsT)null)uidgid)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareHits f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/079_add_uid_gid_fields.pymigrater sXn-J  O & & & O & & &c N|jd}||dddS)Nrrr)r remove_fieldsr s rrollbackrs,n-J :ue44444r)F)loggingpeeweer getLogger__name__loggerrrrrrs`  8 $ $555555rdefence360agent/migrations/__pycache__/079_add_uid_gid_fields.cpython-311.pyc0000644000000000000000000000211000000000000023767 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdtjddS)N malware_hitsT)null)uidgid)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareHits f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/079_add_uid_gid_fields.pymigrater sXn-J  O & & & O & & &c N|jd}||dddS)Nrrr)r remove_fieldsr s rrollbackrs,n-J :ue44444r)F)loggingpeeweer getLogger__name__loggerrrrrrs`  8 $ $555555rdefence360agent/migrations/__pycache__/080_populate_uid_gid_size_hash_fields.cpython-311.opt-1.pyc0000644000000000000000000000703000000000000030062 0ustar r_j fddlZddlZddlZddlZddlZejeZddZddZ ddZ dS) Nc|}d} tj||}|sn(|||t|z }@||fS)zARead an open file descriptor in chunks; return (hexdigest, size).r)osreadupdatelen hexdigest)fd hash_func chunksizehash_sizechunks u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/080_populate_uid_gid_size_hash_fields.py_hash_and_size_from_fdr sq IKKE DI&&   U E   ??  d ""Fc N|jd} |D]}|jdd} t j|tjtjztjz}nn#t$rt d|Yt$r;}|j tjkr t d|Yd}~d}~wwxYw t j|} t!j| js2t d| t j|(|j6|js/t j|dd| j| jc|_|_t7|t8j\|_|_t j|n#t j|wxYw| dS#tB$r%}t"|Yd}~dSd}~wwxYw) N malware_hitszutf-8surrogateescape)errorsz(Malware file %s does not exist, skippingz&Malware file %s is a symlink, skippingz/Malware file %s is not a regular file, skippingr)#ormselect orig_fileencoderopenO_RDONLY O_NOFOLLOW O_NONBLOCKFileNotFoundErrorloggerwarningOSErrorerrnoELOOPfstatstatS_ISREGst_modeclosemoderestoredfchownst_uidst_giduidgidrhashlibsha256hashrsave Exception exception) migratordatabasefakekwargs MalwareHithitpathr ests rmigrater@s4n-J*$$&&' ' C=''8I'JJD  WK"-/"-?%   >   7ek))NN@$HHHH   Xb\\|BJ//NNI 8' 'Ib!Q'''')y")$CGSW%;B%O%O"#(  HHJJJJO' ' P sy3G59A=<G5=%C("G5$ C(-0C#G5"C##C((G5,AG5G5 A$G/G5GG55 H$?HH$c dS)N)r7r8r9r:s rrollbackrCFsDr)r)F) r#r1loggingrr& getLogger__name__r rr@rCrBrrrGs   8 $ $ # # # #,,,,^      rdefence360agent/migrations/__pycache__/080_populate_uid_gid_size_hash_fields.cpython-311.pyc0000644000000000000000000000703000000000000027123 0ustar r_j fddlZddlZddlZddlZddlZejeZddZddZ ddZ dS) Nc|}d} tj||}|sn(|||t|z }@||fS)zARead an open file descriptor in chunks; return (hexdigest, size).r)osreadupdatelen hexdigest)fd hash_func chunksizehash_sizechunks u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/080_populate_uid_gid_size_hash_fields.py_hash_and_size_from_fdr sq IKKE DI&&   U E   ??  d ""Fc N|jd} |D]}|jdd} t j|tjtjztjz}nn#t$rt d|Yt$r;}|j tjkr t d|Yd}~d}~wwxYw t j|} t!j| js2t d| t j|(|j6|js/t j|dd| j| jc|_|_t7|t8j\|_|_t j|n#t j|wxYw| dS#tB$r%}t"|Yd}~dSd}~wwxYw) N malware_hitszutf-8surrogateescape)errorsz(Malware file %s does not exist, skippingz&Malware file %s is a symlink, skippingz/Malware file %s is not a regular file, skippingr)#ormselect orig_fileencoderopenO_RDONLY O_NOFOLLOW O_NONBLOCKFileNotFoundErrorloggerwarningOSErrorerrnoELOOPfstatstatS_ISREGst_modeclosemoderestoredfchownst_uidst_giduidgidrhashlibsha256hashrsave Exception exception) migratordatabasefakekwargs MalwareHithitpathr ests rmigrater@s4n-J*$$&&' ' C=''8I'JJD  WK"-/"-?%   >   7ek))NN@$HHHH   Xb\\|BJ//NNI 8' 'Ib!Q'''')y")$CGSW%;B%O%O"#(  HHJJJJO' ' P sy3G59A=<G5=%C("G5$ C(-0C#G5"C##C((G5,AG5G5 A$G/G5GG55 H$?HH$c dS)N)r7r8r9r:s rrollbackrCFsDr)r)F) r#r1loggingrr& getLogger__name__r rr@rCrBrrrGs   8 $ $ # # # #,,,,^      rdefence360agent/migrations/__pycache__/081_fix_clamscan_broken_symlink.cpython-311.opt-1.pyc0000644000000000000000000000076600000000000026731 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/081_fix_clamscan_broken_symlink.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/081_fix_clamscan_broken_symlink.cpython-311.pyc0000644000000000000000000000076600000000000025772 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/081_fix_clamscan_broken_symlink.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/082_add_cl_on_premise_backup_option.cpython-311.opt-1.pyc0000644000000000000000000000077200000000000027535 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_cl_on_premise_backup_option.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/082_add_cl_on_premise_backup_option.cpython-311.pyc0000644000000000000000000000077200000000000026576 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_cl_on_premise_backup_option.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/082_add_manual_flag.cpython-311.opt-1.pyc0000644000000000000000000000163200000000000024244 0ustar r_j\ddlZddZddZdS)NFc v|jd}||tjdddS)NiplistFT)nulldefault)manual)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_manual_flag.pymigratersI \( #F rE4@@@c L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s* \( #F 68,,,,,r)F)peeweer rrrrrsC------rdefence360agent/migrations/__pycache__/082_add_manual_flag.cpython-311.pyc0000644000000000000000000000163200000000000023305 0ustar r_j\ddlZddZddZdS)NFc v|jd}||tjdddS)NiplistFT)nulldefault)manual)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/082_add_manual_flag.pymigratersI \( #F rE4@@@c L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s* \( #F 68,,,,,r)F)peeweer rrrrrsC------rdefence360agent/migrations/__pycache__/083_drop_no_captcha_field.cpython-311.opt-1.pyc0000644000000000000000000000311300000000000025451 0ustar r_jaddlZddZddZdS)NFc |jd}|d|d|d|d|d||ddS)Niplistz0UPDATE iplist SET manual=0 WHERE listname='GRAY'z1UPDATE iplist SET manual=1 WHERE listname='WHITE'z1UPDATE iplist SET manual=1 WHERE listname='BLACK'zHUPDATE iplist SET listname='BLACK'WHERE listname='GRAY' AND no_captcha=1zUPDATE iplist SET comment='Automatically blocked due to distributed attack', imported_from='Imunify360' WHERE listname='BLACK' AND manual=0 no_captcha)ormsql remove_fieldsmigratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/083_drop_no_captcha_field.pymigraters \( #F LLCDDD LLDEEE LLDEEE LL 1 LL /  6<00000c t|jd}||tjddS)NrF)default)r)r add_fieldspw BooleanFieldr s rrollbackrs: \( #F 2?5+I+I+IJJJJJr)F)peeweerrrrrrsI1111$KKKKKKrdefence360agent/migrations/__pycache__/083_drop_no_captcha_field.cpython-311.pyc0000644000000000000000000000311300000000000024512 0ustar r_jaddlZddZddZdS)NFc |jd}|d|d|d|d|d||ddS)Niplistz0UPDATE iplist SET manual=0 WHERE listname='GRAY'z1UPDATE iplist SET manual=1 WHERE listname='WHITE'z1UPDATE iplist SET manual=1 WHERE listname='BLACK'zHUPDATE iplist SET listname='BLACK'WHERE listname='GRAY' AND no_captcha=1zUPDATE iplist SET comment='Automatically blocked due to distributed attack', imported_from='Imunify360' WHERE listname='BLACK' AND manual=0 no_captcha)ormsql remove_fieldsmigratordatabasefakekwargsIPLists i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/083_drop_no_captcha_field.pymigraters \( #F LLCDDD LLDEEE LLDEEE LL 1 LL /  6<00000c t|jd}||tjddS)NrF)default)r)r add_fieldspw BooleanFieldr s rrollbackrs: \( #F 2?5+I+I+IJJJJJr)F)peeweerrrrrrsI1111$KKKKKKrdefence360agent/migrations/__pycache__/084_country_subnets_fields.cpython-311.opt-1.pyc0000644000000000000000000000240500000000000025763 0ustar r_jddlZddZddZdS)NFc |jd}||dd||tjdtjdtjddS)Ncountry_subnetsip_netipT)null)network_addressnetmaskversion)orm rename_field add_fieldspw IntegerFieldmigratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/084_country_subnets_fields.pymigraters\"34N .(D999 T222T***T*** c ~|jd}||dd||ddddS)Nrrrrr r )r r remove_fieldsrs rrollbackrsS\"34N .$999 )9ir)F)peeweerrrrrrsCrdefence360agent/migrations/__pycache__/084_country_subnets_fields.cpython-311.pyc0000644000000000000000000000240500000000000025024 0ustar r_jddlZddZddZdS)NFc |jd}||dd||tjdtjdtjddS)Ncountry_subnetsip_netipT)null)network_addressnetmaskversion)orm rename_field add_fieldspw IntegerFieldmigratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/084_country_subnets_fields.pymigraters\"34N .(D999 T222T***T*** c ~|jd}||dd||ddddS)Nrrrrr r )r r remove_fieldsrs rrollbackrsS\"34N .$999 )9ir)F)peeweerrrrrrsCrdefence360agent/migrations/__pycache__/085_country_subnets_fields.cpython-311.opt-1.pyc0000644000000000000000000000231200000000000025761 0ustar r_j>ddlZejeZddZddZdS)NFc |rdS|jd}|d||d||d||ddS)Ncountry_subnetszDELETE FROM country_subnetsnetwork_addressnetmaskversion)ormsql add_not_nullmigratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/085_country_subnets_fields.pymigraters \"34N LL./// .*;<<<  .)44444c P|jd}||ddddS)Nrrrr)r drop_not_nullr s rrollbackrs;\"34N )9ir)F)logging getLogger__name__loggerrrrrrsR  8 $ $ 5 5 5 5rdefence360agent/migrations/__pycache__/085_country_subnets_fields.cpython-311.pyc0000644000000000000000000000231200000000000025022 0ustar r_j>ddlZejeZddZddZdS)NFc |rdS|jd}|d||d||d||ddS)Ncountry_subnetszDELETE FROM country_subnetsnetwork_addressnetmaskversion)ormsql add_not_nullmigratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/085_country_subnets_fields.pymigraters \"34N LL./// .*;<<<  .)44444c P|jd}||ddddS)Nrrrr)r drop_not_nullr s rrollbackrs;\"34N )9ir)F)logging getLogger__name__loggerrrrrrsR  8 $ $ 5 5 5 5rdefence360agent/migrations/__pycache__/086_ignored_by_port_fields.cpython-311.opt-1.pyc0000644000000000000000000000240000000000000025677 0ustar r_jddlZddZddZdS)NFc |jd}|jd}||tjdtjdtjdtj|ddS)Nignored_by_port_protocountryT)null)network_addressnetmaskversionr)orm add_fieldspw IntegerFieldForeignKeyField)migratordatabasefakekwargs IgnoredByPortCountrys j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/086_ignored_by_port_fields.pymigratersL!89Ml9%G T222T***T***"7666 c R|jd}||dddddS)Nrrrr r)r remove_fields)rrrrrs rrollbackrs=L!89M ()Y r)F)peeweer rrrrrsC    rdefence360agent/migrations/__pycache__/086_ignored_by_port_fields.cpython-311.pyc0000644000000000000000000000240000000000000024740 0ustar r_jddlZddZddZdS)NFc |jd}|jd}||tjdtjdtjdtj|ddS)Nignored_by_port_protocountryT)null)network_addressnetmaskversionr)orm add_fieldspw IntegerFieldForeignKeyField)migratordatabasefakekwargs IgnoredByPortCountrys j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/086_ignored_by_port_fields.pymigratersL!89Ml9%G T222T***T***"7666 c R|jd}||dddddS)Nrrrr r)r remove_fields)rrrrrs rrollbackrs=L!89M ()Y r)F)peeweer rrrrrsC    rdefence360agent/migrations/__pycache__/087_ignored_by_port_fields.cpython-311.opt-1.pyc0000644000000000000000000000665100000000000025714 0ustar r_jJddlZddlmZejeZddZddZdS)N) ip_networkFc |jd} ddlm}||j}d|D}n#t$rg}YnwxYw|D]} |t|\} } } | | | |  |j|k f#t$r`td|| |j|k YwxYw|rddlm}  | 5} |D][}| |}| | |j|k \ dddn #1swxYwYn*#t($rtd YnwxYw||d ||d ||d dS) Nignored_by_port_protor)pack_ip_networkcg|]\}|Sr).0ips j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/087_ignored_by_port_fields.py zmigrate..scbr)network_addressnetmaskversionzInvalid IP network %s)geo)countryz2Failed to update countries data in ignored_by_portrrr)ormim360.utils.netrselectr distincttuples ImportErrorrupdatewhereexecute ValueErrorloggerwarningdeletedefence360agent.internalsrreaderget_idOSError add_not_null)migratordatabasefakekwargs IgnoredByPortrqipsr netmaskrr geo_readerrs r migrater/sL!89M 333333  !1 2 2 ; ; = = D D F FQ   6 6 6!0B!@!@ Cw  #T7 !  eM$*++GGIIII  K K K NN2B 7 7 7  " " ( ()9R)? @ @ H H J J J J J K 111111  >>>B(//33G!(( ')eM,233GGIIII > > > > > > > > > > > > > > > >     NND        -):;;;  -33333s[A A/.A/7CA'EEG$AG GGGGG$HHc P|jd}||ddddS)Nrrrr)r drop_not_null)r%r&r'r(r)s r rollbackr23s;L!89M ()Yr )F)logging ipaddressr getLogger__name__rr/r2rr r r7se  8 $ $)4)4)4)4Xr defence360agent/migrations/__pycache__/087_ignored_by_port_fields.cpython-311.pyc0000644000000000000000000000665100000000000024755 0ustar r_jJddlZddlmZejeZddZddZdS)N) ip_networkFc |jd} ddlm}||j}d|D}n#t$rg}YnwxYw|D]} |t|\} } } | | | |  |j|k f#t$r`td|| |j|k YwxYw|rddlm}  | 5} |D][}| |}| | |j|k \ dddn #1swxYwYn*#t($rtd YnwxYw||d ||d ||d dS) Nignored_by_port_protor)pack_ip_networkcg|]\}|Sr).0ips j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/087_ignored_by_port_fields.py zmigrate..scbr)network_addressnetmaskversionzInvalid IP network %s)geo)countryz2Failed to update countries data in ignored_by_portrrr)ormim360.utils.netrselectr distincttuples ImportErrorrupdatewhereexecute ValueErrorloggerwarningdeletedefence360agent.internalsrreaderget_idOSError add_not_null)migratordatabasefakekwargs IgnoredByPortrqipsr netmaskrr geo_readerrs r migrater/sL!89M 333333  !1 2 2 ; ; = = D D F FQ   6 6 6!0B!@!@ Cw  #T7 !  eM$*++GGIIII  K K K NN2B 7 7 7  " " ( ()9R)? @ @ H H J J J J J K 111111  >>>B(//33G!(( ')eM,233GGIIII > > > > > > > > > > > > > > > >     NND        -):;;;  -33333s[A A/.A/7CA'EEG$AG GGGGG$HHc P|jd}||ddddS)Nrrrr)r drop_not_null)r%r&r'r(r)s r rollbackr23s;L!89M ()Yr )F)logging ipaddressr getLogger__name__rr/r2rr r r7se  8 $ $)4)4)4)4Xr defence360agent/migrations/__pycache__/088_add_malware_i360_clamd_scan_option.cpython-311.opt-1.pyc0000644000000000000000000000077500000000000027740 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/088_add_malware_i360_clamd_scan_option.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/088_add_malware_i360_clamd_scan_option.cpython-311.pyc0000644000000000000000000000077500000000000027001 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/088_add_malware_i360_clamd_scan_option.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/089_proactive_tables.cpython-311.opt-1.pyc0000644000000000000000000000663100000000000024527 0ustar r_jddlZddZddZdS)NFc |jdGfddtjGfddtj}|||dS)Ncountryc&eZdZGddZejZejdZej dZ ejdZ ejdZ ej dZej dZej dZej dZej dZej dZej dZejdZejdZejdZdS)migrate..ProactiveceZdZdZdS)migrate..Proactive.Meta proactiveN)__name__ __module__ __qualname__db_tabled/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/089_proactive_tables.pyMetars"HHHrrFnullTN)r r r rpwPrimaryKeyFieldid IntegerField timestamp TextFieldipip_int ip_versionForeignKeyField ip_countryreason descriptionactionhostpathurlcountuidgid)Countrysr Proactiversd # # # # # # # # R  ! !#BO/// R\t $ $ $ d+++$R_$/// 'R'd;;; 5)))"bl--- 5)))r|&&&r|'''bl%%%U+++bo5)))bo5)))rr)ceZdZejdddZejdZejdZGddZ d S) migrate..ProactiveEnvFCASCADEenv)r on_delete related_namerTc6eZdZdZejdddZdS)"migrate..ProactiveEnv.Meta proactive_enveventnamevalueN)r r r r r CompositeKey primary_keyrrrrr1"s(&H)"/'67CCKKKrrN) r r r rrr3rr4r5r)r)sr ProactiveEnvr+s"" EYU   r|''' $''' D D D D D D D D D Drr8)ormrModel create_model)migratordatabasefakekwargsr8r(r)s @@rmigrater@sl9%G*******BH***( D D D D D D Drx D D D )$$$ ,'''''rc |jd}|jd}||||dS)Nr2r )r9 remove_model)r<r=r>r?r8r)s rrollbackrC*sH<0L [)I ,''' )$$$$$r)F)peeweerr@rCrrrrEsD#(#(#(#(L%%%%%%rdefence360agent/migrations/__pycache__/089_proactive_tables.cpython-311.pyc0000644000000000000000000000663100000000000023570 0ustar r_jddlZddZddZdS)NFc |jdGfddtjGfddtj}|||dS)Ncountryc&eZdZGddZejZejdZej dZ ejdZ ejdZ ej dZej dZej dZej dZej dZej dZej dZejdZejdZejdZdS)migrate..ProactiveceZdZdZdS)migrate..Proactive.Meta proactiveN)__name__ __module__ __qualname__db_tabled/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/089_proactive_tables.pyMetars"HHHrrFnullTN)r r r rpwPrimaryKeyFieldid IntegerField timestamp TextFieldipip_int ip_versionForeignKeyField ip_countryreason descriptionactionhostpathurlcountuidgid)Countrysr Proactiversd # # # # # # # # R  ! !#BO/// R\t $ $ $ d+++$R_$/// 'R'd;;; 5)))"bl--- 5)))r|&&&r|'''bl%%%U+++bo5)))bo5)))rr)ceZdZejdddZejdZejdZGddZ d S) migrate..ProactiveEnvFCASCADEenv)r on_delete related_namerTc6eZdZdZejdddZdS)"migrate..ProactiveEnv.Meta proactive_enveventnamevalueN)r r r r r CompositeKey primary_keyrrrrr1"s(&H)"/'67CCKKKrrN) r r r rrr3rr4r5r)r)sr ProactiveEnvr+s"" EYU   r|''' $''' D D D D D D D D D Drr8)ormrModel create_model)migratordatabasefakekwargsr8r(r)s @@rmigrater@sl9%G*******BH***( D D D D D D Drx D D D )$$$ ,'''''rc |jd}|jd}||||dS)Nr2r )r9 remove_model)r<r=r>r?r8r)s rrollbackrC*sH<0L [)I ,''' )$$$$$r)F)peeweerr@rCrrrrEsD#(#(#(#(L%%%%%%rdefence360agent/migrations/__pycache__/090_safe_user_config.cpython-311.opt-1.pyc0000644000000000000000000000470600000000000024473 0ustar r_jybddlZddlZddlZddlZddlmZejeZddZ ddZ dS)N)CoreFc  tjD]} tj|jt j}tj|rtj |stjt j |j }tj |tj |d|jtj|dtj|t j}t!j||tj |d|jtj|dV#t$$r3}t&dt+|Yd}~d}~wwxYwdS#t,$rt&d|YdSwxYw)NriizSomething went wrong: %szFailed to migrate config for %s)pwdgetpwallospathjoinpw_dirrUSER_CONFIG_FILE_NAMEisfileislink USER_CONFDIRpw_namemkdirchownpw_gidchmodshutilmoveOSErrorloggerwarningstr Exception exception) migratordatabasefakekwargsusersrcdst_dirdst_filees d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/090_safe_user_config.pymigrater& sBLNN C CD Cgll4;0JKK7>>#&& .rw~~c/B/B . gll4+r/s 111111  8 $ $BBBB,      r*defence360agent/migrations/__pycache__/090_safe_user_config.cpython-311.pyc0000644000000000000000000000470600000000000023534 0ustar r_jybddlZddlZddlZddlZddlmZejeZddZ ddZ dS)N)CoreFc  tjD]} tj|jt j}tj|rtj |stjt j |j }tj |tj |d|jtj|dtj|t j}t!j||tj |d|jtj|dV#t$$r3}t&dt+|Yd}~d}~wwxYwdS#t,$rt&d|YdSwxYw)NriizSomething went wrong: %szFailed to migrate config for %s)pwdgetpwallospathjoinpw_dirrUSER_CONFIG_FILE_NAMEisfileislink USER_CONFDIRpw_namemkdirchownpw_gidchmodshutilmoveOSErrorloggerwarningstr Exception exception) migratordatabasefakekwargsusersrcdst_dirdst_filees d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/090_safe_user_config.pymigrater& sBLNN C CD Cgll4;0JKK7>>#&& .rw~~c/B/B . gll4+r/s 111111  8 $ $BBBB,      r*defence360agent/migrations/__pycache__/091_compress_old_logs.cpython-311.opt-1.pyc0000644000000000000000000000423700000000000024707 0ustar r_jnddlZddlZddlZddlZddlmZddlmZeje Z ddZ ddZ dS)N)Logger)get_log_file_namesFc &tD]}tdtjdzD]}|d|}|d} tj|r~t|d5}tj|d5} tj || dddn #1swxYwYdddn #1swxYwYt j |#t$r&} td|| Yd} ~ d} ~ wwxYwdS)N.z.gzrbwbz"Failed file %s compression with %s)rranger BACKUP_COUNTospathexistsopengzipshutil copyfileobjremove Exceptionlogger exception) migratordatabasefakekwargsfilenameisourcedestf_inf_outes e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/091_compress_old_logs.pymigrater# s&((q&-122  A &&1&&F>>>D 7>>&))&fd++8tTYd668*4777888888888888888888888888888888If%%%     8&!  s_/C-B<B%  B<%B) )B<,B) -B<0 C<C CC C D &DD c dS)z$Write your rollback migrations here.N)rrrrs r"rollbackr&sD)F) loggingrrr defence360agent.contracts.configr defence360agent.internals.loggerr getLogger__name__rr#r&r%r'r"r-s 333333??????  8 $ $$      r'defence360agent/migrations/__pycache__/091_compress_old_logs.cpython-311.pyc0000644000000000000000000000423700000000000023750 0ustar r_jnddlZddlZddlZddlZddlmZddlmZeje Z ddZ ddZ dS)N)Logger)get_log_file_namesFc &tD]}tdtjdzD]}|d|}|d} tj|r~t|d5}tj|d5} tj || dddn #1swxYwYdddn #1swxYwYt j |#t$r&} td|| Yd} ~ d} ~ wwxYwdS)N.z.gzrbwbz"Failed file %s compression with %s)rranger BACKUP_COUNTospathexistsopengzipshutil copyfileobjremove Exceptionlogger exception) migratordatabasefakekwargsfilenameisourcedestf_inf_outes e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/091_compress_old_logs.pymigrater# s&((q&-122  A &&1&&F>>>D 7>>&))&fd++8tTYd668*4777888888888888888888888888888888If%%%     8&!  s_/C-B<B%  B<%B) )B<,B) -B<0 C<C CC C D &DD c dS)z$Write your rollback migrations here.N)rrrrs r"rollbackr&sD)F) loggingrrr defence360agent.contracts.configr defence360agent.internals.loggerr getLogger__name__rr#r&r%r'r"r-s 333333??????  8 $ $$      r'defence360agent/migrations/__pycache__/092_ignore_proc_sys_dirs.cpython-311.opt-1.pyc0000644000000000000000000000154600000000000025420 0ustar r_jdZddZddZdS)zC This migration adds /proc and /sys to ignore for malware scanning Fc ^|s(dD]'}|jd}||&dSdS)N)z/procz/sysmalware_ignore_path)path)orm get_or_create)migratordatabasefakekwargs ignored_dirMalwareIgnorePaths h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_ignore_proc_sys_dirs.pymigratersU >, > >K ( -B C   + + + = = = =>> > >c dS)N)rrr r s r rollbackr sDrN)F)__doc__rrrrr rsA >>>>      rdefence360agent/migrations/__pycache__/092_ignore_proc_sys_dirs.cpython-311.pyc0000644000000000000000000000154600000000000024461 0ustar r_jdZddZddZdS)zC This migration adds /proc and /sys to ignore for malware scanning Fc ^|s(dD]'}|jd}||&dSdS)N)z/procz/sysmalware_ignore_path)path)orm get_or_create)migratordatabasefakekwargs ignored_dirMalwareIgnorePaths h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_ignore_proc_sys_dirs.pymigratersU >, > >K ( -B C   + + + = = = =>> > >c dS)N)rrr r s r rollbackr sDrN)F)__doc__rrrrr rsA >>>>      rdefence360agent/migrations/__pycache__/092_remove_old_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000132000000000000026355 0ustar r_jOdZddZddZdS)z Moves disabled rules from the cPanel-specific user data directory to the centralized Apache configuration directory. No longer required running due to age and causing issues with the Coraza WAF Fc dSNmigratordatabasefakekwargss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_remove_old_disabled_rules.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/092_remove_old_disabled_rules.cpython-311.pyc0000644000000000000000000000132000000000000025416 0ustar r_jOdZddZddZdS)z Moves disabled rules from the cPanel-specific user data directory to the centralized Apache configuration directory. No longer required running due to age and causing issues with the Coraza WAF Fc dSNmigratordatabasefakekwargss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/092_remove_old_disabled_rules.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA          r defence360agent/migrations/__pycache__/093_make_quarantined_files_immutable.cpython-311.opt-1.pyc0000644000000000000000000000077300000000000027726 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/093_make_quarantined_files_immutable.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/093_make_quarantined_files_immutable.cpython-311.pyc0000644000000000000000000000077300000000000026767 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/093_make_quarantined_files_immutable.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/094_ignore_cagefs_proc.cpython-311.opt-1.pyc0000644000000000000000000000154200000000000025007 0ustar r_jydZddZddZdS)zU This migration adds /usr/share/cagefs-skeleton/proc/ to ignore for malware scanning Fc T|s%|jd}|ddSdS)Nmalware_ignore_pathz/usr/share/cagefs-skeleton/proc)path)orm get_or_create)migratordatabasefakekwargsMalwareIgnorePaths f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/094_ignore_cagefs_proc.pymigrater sC P$L)>?''-N'OOOOOPPc dS)N)rrr r s r rollbackr sDrN)F)__doc__r rrrr rsE PPPP       rdefence360agent/migrations/__pycache__/094_ignore_cagefs_proc.cpython-311.pyc0000644000000000000000000000154200000000000024050 0ustar r_jydZddZddZdS)zU This migration adds /usr/share/cagefs-skeleton/proc/ to ignore for malware scanning Fc T|s%|jd}|ddSdS)Nmalware_ignore_pathz/usr/share/cagefs-skeleton/proc)path)orm get_or_create)migratordatabasefakekwargsMalwareIgnorePaths f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/094_ignore_cagefs_proc.pymigrater sC P$L)>?''-N'OOOOOPPc dS)N)rrr r s r rollbackr sDrN)F)__doc__r rrrr rsE PPPP       rdefence360agent/migrations/__pycache__/095_add_total_malicious_field.cpython-311.opt-1.pyc0000644000000000000000000000167700000000000026346 0ustar r_jddlZddZddZdS)NFc v|jd}||tjdddS)N malware_scansFr)nulldefault)total_malicious)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareScans m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/095_add_total_malicious_field.pymigratersJ,/K UA>>>c L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+,/K ;(9:::::r)F)peeweer rrrrrsC;;;;;;rdefence360agent/migrations/__pycache__/095_add_total_malicious_field.cpython-311.pyc0000644000000000000000000000167700000000000025407 0ustar r_jddlZddZddZdS)NFc v|jd}||tjdddS)N malware_scansFr)nulldefault)total_malicious)orm add_fieldspw IntegerFieldmigratordatabasefakekwargs MalwareScans m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/095_add_total_malicious_field.pymigratersJ,/K UA>>>c L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+,/K ;(9:::::r)F)peeweer rrrrrsC;;;;;;rdefence360agent/migrations/__pycache__/096_populate_total_malicious_field.cpython-311.opt-1.pyc0000644000000000000000000000204600000000000027437 0ustar r_jddZddZdS)Fc |rdS|jd}|jd}|D]`}|j|j}||_|adS)N malware_scans malware_hits)ormmalwarehit_setselectwhere maliciouscounttotal_malicioussave)migratordatabasefakekwargs MalwareScan MalwareHitscanr s r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/096_populate_total_malicious_field.pymigraters ,/Kn-J   & & ( ( . .z/C D D J J L L  / c dS)N)r rrrs rrollbackrsDrN)F)rrrrrrs7          rdefence360agent/migrations/__pycache__/096_populate_total_malicious_field.cpython-311.pyc0000644000000000000000000000204600000000000026500 0ustar r_jddZddZdS)Fc |rdS|jd}|jd}|D]`}|j|j}||_|adS)N malware_scans malware_hits)ormmalwarehit_setselectwhere maliciouscounttotal_malicioussave)migratordatabasefakekwargs MalwareScan MalwareHitscanr s r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/096_populate_total_malicious_field.pymigraters ,/Kn-J   & & ( ( . .z/C D D J J L L  / c dS)N)r rrrs rrollbackrsDrN)F)rrrrrrs7          rdefence360agent/migrations/__pycache__/097_remove_uid_and_gid.cpython-311.opt-1.pyc0000644000000000000000000000176700000000000025010 0ustar r_jX>ddlZejeZddZddZdS)NFc |jd} ||dddS#t$r%}t|Yd}~dSd}~wwxYw)N malware_hitsuidgid)orm remove_fields Exceptionlogger exception)migratordatabasefakekwargs MalwareHites f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/097_remove_uid_and_gid.pymigratersvn-Jz5%88888 s( AAAc dS)N)r r rrs rrollbackrsD)F)logging getLogger__name__r rrrrrrsR  8 $ $      rdefence360agent/migrations/__pycache__/097_remove_uid_and_gid.cpython-311.pyc0000644000000000000000000000176700000000000024051 0ustar r_jX>ddlZejeZddZddZdS)NFc |jd} ||dddS#t$r%}t|Yd}~dSd}~wwxYw)N malware_hitsuidgid)orm remove_fields Exceptionlogger exception)migratordatabasefakekwargs MalwareHites f/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/097_remove_uid_and_gid.pymigratersvn-Jz5%88888 s( AAAc dS)N)r r rrs rrollbackrsD)F)logging getLogger__name__r rrrrrrsR  8 $ $      rdefence360agent/migrations/__pycache__/098_remote_proxy_tables.cpython-311.opt-1.pyc0000644000000000000000000000533000000000000025262 0ustar r_jddlZddZddZdS)NFc GddtjGfddtj}|||dS)Nc eZdZdZdZdZejdZejdej d eegZ ej dd Z Gd d Zd S) !migrate..RemoteProxyGroupz9Groups multiple remote proxies together with common data.manual imunify360Fnullzsource in ('{}', '{}'))r constraintsT)r defaultceZdZdZdZdS)&migrate..RemoteProxyGroup.Metaremote_proxy_group)))namesourceTN)__name__ __module__ __qualname__db_tableindexesg/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/098_remote_proxy_tables.pyMetar s+H3GGGrrN)rrr__doc__MANUAL IMUNIFY360pw CharFieldrCheckformatr BooleanFieldenabledrrrrRemoteProxyGrouprsGG! r|'''188LLMM    ""/ud;;; 4 4 4 4 4 4 4 4 4 4rr#cpeZdZejdZejdZGddZdS)migrate..RemoteProxyFrceZdZdZdS)!migrate..RemoteProxy.Meta remote_proxyN)rrrrrrrrr's%HHHrrN) rrrrForeignKeyFieldgroup TextFieldnetworkr)r#sr RemoteProxyr%si""#3%@@@",E*** & & & & & & & & & &rr-)rModel create_modelmigratordatabasefakekwargsr-r#s @rmigrater5s4444428444$&&&&&&&bh&&& *+++ +&&&&&rc |jd}|jd}||||dS)Nr(r)orm remove_modelr0s rrollbackr9"sK,~.K|$89 +&&& *+++++r)F)peeweerr5r9rrrr;sC''''<,,,,,,rdefence360agent/migrations/__pycache__/098_remote_proxy_tables.cpython-311.pyc0000644000000000000000000000533000000000000024323 0ustar r_jddlZddZddZdS)NFc GddtjGfddtj}|||dS)Nc eZdZdZdZdZejdZejdej d eegZ ej dd Z Gd d Zd S) !migrate..RemoteProxyGroupz9Groups multiple remote proxies together with common data.manual imunify360Fnullzsource in ('{}', '{}'))r constraintsT)r defaultceZdZdZdZdS)&migrate..RemoteProxyGroup.Metaremote_proxy_group)))namesourceTN)__name__ __module__ __qualname__db_tableindexesg/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/098_remote_proxy_tables.pyMetar s+H3GGGrrN)rrr__doc__MANUAL IMUNIFY360pw CharFieldrCheckformatr BooleanFieldenabledrrrrRemoteProxyGrouprsGG! r|'''188LLMM    ""/ud;;; 4 4 4 4 4 4 4 4 4 4rr#cpeZdZejdZejdZGddZdS)migrate..RemoteProxyFrceZdZdZdS)!migrate..RemoteProxy.Meta remote_proxyN)rrrrrrrrr's%HHHrrN) rrrrForeignKeyFieldgroup TextFieldnetworkr)r#sr RemoteProxyr%si""#3%@@@",E*** & & & & & & & & & &rr-)rModel create_modelmigratordatabasefakekwargsr-r#s @rmigrater5s4444428444$&&&&&&&bh&&& *+++ +&&&&&rc |jd}|jd}||||dS)Nr(r)orm remove_modelr0s rrollbackr9"sK,~.K|$89 +&&& *+++++r)F)peeweerr5r9rrrr;sC''''<,,,,,,rdefence360agent/migrations/__pycache__/099_remove_old_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000423200000000000026371 0ustar r_j`ddlZddlZddlZddlZddlmZmZejeZ dZ ej ddZ ddZ dS)N)antivirus_moderun_coroz,/etc/apache2/conf.d/i360_modsec_disable.confFc ,|rdS ddlm}n#t$rYdSwxYw |r!t |sdS|}t jtr]tj tt j |j |jtj|jdSdS#t$$r&}t&d|Yd}~dSd}~wwxYw)Nr)cPanelz)Failed to delete old rules config with %s)im360.subsys.panels.cpanelr ImportError is_installedrinstalled_modsecospathexistsOLD_DISABLED_RULES_CONFIGshutilmovejoinDISABLED_RULES_CONFIG_DIR%GLOBAL_DISABLED_RULES_CONFIG_FILENAME subprocess check_callREBUILD_HTTPDCONF_CMD Exceptionlogger exception)migratordatabasefakekwargsrhpes m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/099_remove_old_disabled_rules.pymigrater! sX 5555555 I""$$ H  # # % %- -   F VXX 7>>3 4 4 < K) 0<     !"": ; ; ; ; ; < < IIIDaHHHHHHHHHIs* 5C#B C## D-DDc dS)N)rrrrs r rollbackr$,sD)F)loggingr rrdefence360agent.utilsrr getLogger__name__rrskipr!r$r#r%r r+s ::::::::  8 $ $JIIII<      r%defence360agent/migrations/__pycache__/099_remove_old_disabled_rules.cpython-311.pyc0000644000000000000000000000423200000000000025432 0ustar r_j`ddlZddlZddlZddlZddlmZmZejeZ dZ ej ddZ ddZ dS)N)antivirus_moderun_coroz,/etc/apache2/conf.d/i360_modsec_disable.confFc ,|rdS ddlm}n#t$rYdSwxYw |r!t |sdS|}t jtr]tj tt j |j |jtj|jdSdS#t$$r&}t&d|Yd}~dSd}~wwxYw)Nr)cPanelz)Failed to delete old rules config with %s)im360.subsys.panels.cpanelr ImportError is_installedrinstalled_modsecospathexistsOLD_DISABLED_RULES_CONFIGshutilmovejoinDISABLED_RULES_CONFIG_DIR%GLOBAL_DISABLED_RULES_CONFIG_FILENAME subprocess check_callREBUILD_HTTPDCONF_CMD Exceptionlogger exception)migratordatabasefakekwargsrhpes m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/099_remove_old_disabled_rules.pymigrater! sX 5555555 I""$$ H  # # % %- -   F VXX 7>>3 4 4 < K) 0<     !"": ; ; ; ; ; < < IIIDaHHHHHHHHHIs* 5C#B C## D-DDc dS)N)rrrrs r rollbackr$,sD)F)loggingr rrdefence360agent.utilsrr getLogger__name__rrskipr!r$r#r%r r+s ::::::::  8 $ $JIIII<      r%defence360agent/migrations/__pycache__/100_remove_captcha_ports_from_csf.cpython-311.opt-1.pyc0000644000000000000000000000264400000000000027246 0ustar r_j;FddlZddlZejeZddZddZdS)NFc (|rdS ddlm}ddlm}m}n#t $rYdSwxYwt j|j sdS | ||dddddS#t$rt dYdSwxYw) Nr)csf)INTCPiiiiz.Failed to remove captcha ports from csf config) im360.subsysrim360.utils.netrr ImportErrorospathisfile CSF_CONFIG remove_ports Exceptionlogger exception)migratordatabasefakekwargsrrrs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/100_remove_captcha_ports_from_csf.pymigraters $$$$$$+++++++++  7>>#. ) )K b%u===== KKKIJJJJJJKs ## A))$BBc dS)N)rrrrs rrollbackrsD)F)r logging getLogger__name__rrrrrrrs_   8 $ $KKKK"      rdefence360agent/migrations/__pycache__/100_remove_captcha_ports_from_csf.cpython-311.pyc0000644000000000000000000000264400000000000026307 0ustar r_j;FddlZddlZejeZddZddZdS)NFc (|rdS ddlm}ddlm}m}n#t $rYdSwxYwt j|j sdS | ||dddddS#t$rt dYdSwxYw) Nr)csf)INTCPiiiiz.Failed to remove captcha ports from csf config) im360.subsysrim360.utils.netrr ImportErrorospathisfile CSF_CONFIG remove_ports Exceptionlogger exception)migratordatabasefakekwargsrrrs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/100_remove_captcha_ports_from_csf.pymigraters $$$$$$+++++++++  7>>#. ) )K b%u===== KKKIJJJJJJKs ## A))$BBc dS)N)rrrrs rrollbackrsD)F)r logging getLogger__name__rrrrrrrs_   8 $ $KKKK"      r././@LongLink0000644000000000000000000000015000000000000007767 Lustar defence360agent/migrations/__pycache__/101_remove_unneeded_acronis_ports_from_csf.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/101_remove_unneeded_acronis_ports_from_csf.cpython-311.opt-1.0000644000000000000000000000315300000000000030431 0ustar r_j1FddlZddlZejeZddZddZdS)NFc j|rdS ddlm}ddlm}m}m}n#t $rYdSwxYwtj |j sdS | ||dddddd d h | ||dddd dS#t$rtd YdSwxYw) Nr)csf)INOUTTCPiiiiaii)iZix)rangesz5Failed to remove unused Arconis ports from csf config) im360.subsysrim360.utils.netrrr ImportErrorospathisfile CSF_CONFIG remove_ports Exceptionlogger exception)migratordatabasefakekwargsrrrrs z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.pymigraters2 $$$$$$00000000000  7>>#. ) )           >   c4T:::::     C       s %%9B $B21B2c dS)N)rrrrs rrollbackr&sD)F)r logging getLogger__name__rrrrrrr!s[   8 $ $    >      rdefence360agent/migrations/__pycache__/101_remove_unneeded_acronis_ports_from_csf.cpython-311.pyc0000644000000000000000000000315300000000000030206 0ustar r_j1FddlZddlZejeZddZddZdS)NFc j|rdS ddlm}ddlm}m}m}n#t $rYdSwxYwtj |j sdS | ||dddddd d h | ||dddd dS#t$rtd YdSwxYw) Nr)csf)INOUTTCPiiiiaii)iZix)rangesz5Failed to remove unused Arconis ports from csf config) im360.subsysrim360.utils.netrrr ImportErrorospathisfile CSF_CONFIG remove_ports Exceptionlogger exception)migratordatabasefakekwargsrrrrs z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.pymigraters2 $$$$$$00000000000  7>>#. ) )           >   c4T:::::     C       s %%9B $B21B2c dS)N)rrrrs rrollbackr&sD)F)r logging getLogger__name__rrrrrrr!s[   8 $ $    >      rdefence360agent/migrations/__pycache__/102_proactive_ignore_list.cpython-311.opt-1.pyc0000644000000000000000000000645600000000000025562 0ustar r_jL*ddlmZddlZddZddZdS))timeNFc \GddtjGfddtj}||||jd}||tjd||d d dS) NcteZdZdZejddZejdeZ GddZ dS) %migrate..ProactiveIgnoredPathz3 Ignore list for proactive defence FT)null primary_key)rdefaultceZdZdZdS)*migrate..ProactiveIgnoredPath.Metaproactive_ignored_pathN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_proactive_ignore_list.pyMetar s/HHHrrN) r rr__doc__pw TextFieldpath IntegerFieldr timestamprrrrProactiveIgnoredPathrst  r|D999#BO===  0 0 0 0 0 0 0 0 0 0rrceZdZdZejdddZejdZej dZ GddZ d S) %migrate..ProactiveIgnoredRulez( Specific rules ignored FCASCADErules)r on_delete related_namerceZdZdZdZdS)*migrate..ProactiveIgnoredRule.Metaproactive_ignored_rule)))rrule_idTN)r rrrindexesrrrrr$ s/H4GGGrrN) r rrrrForeignKeyFieldrrr&r rule_namer)rsrProactiveIgnoredRulers  "r!      ""/u--- BLe,,,  5 5 5 5 5 5 5 5 5 5rr* proactiveTr")r&reasonr))rModel create_modelorm add_fieldsr rename_field)migratordatabasefakekwargsr* Proactivers @rmigrater7s 0 0 0 0 0rx 0 0 05555555rx555$ ./// ./// [)I T*** )X{;;;;;rc |jd}|jd}|||||jd}||d||dddS)Nr r%r+r&r)r,)r/ remove_model remove_fieldsr1)r2r3r4r5rr*r6s rrollbackr;/s#<(@A#<(@A ./// ./// [)I 9i000 )[(;;;;;r)F)rpeeweerr7r;rrrr=sV&<&<&<&.ProactiveIgnoredPathz3 Ignore list for proactive defence FT)null primary_key)rdefaultceZdZdZdS)*migrate..ProactiveIgnoredPath.Metaproactive_ignored_pathN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_proactive_ignore_list.pyMetar s/HHHrrN) r rr__doc__pw TextFieldpath IntegerFieldr timestamprrrrProactiveIgnoredPathrst  r|D999#BO===  0 0 0 0 0 0 0 0 0 0rrceZdZdZejdddZejdZej dZ GddZ d S) %migrate..ProactiveIgnoredRulez( Specific rules ignored FCASCADErules)r on_delete related_namerceZdZdZdZdS)*migrate..ProactiveIgnoredRule.Metaproactive_ignored_rule)))rrule_idTN)r rrrindexesrrrrr$ s/H4GGGrrN) r rrrrForeignKeyFieldrrr&r rule_namer)rsrProactiveIgnoredRulers  "r!      ""/u--- BLe,,,  5 5 5 5 5 5 5 5 5 5rr* proactiveTr")r&reasonr))rModel create_modelorm add_fieldsr rename_field)migratordatabasefakekwargsr* Proactivers @rmigrater7s 0 0 0 0 0rx 0 0 05555555rx555$ ./// ./// [)I T*** )X{;;;;;rc |jd}|jd}|||||jd}||d||dddS)Nr r%r+r&r)r,)r/ remove_model remove_fieldsr1)r2r3r4r5rr*r6s rrollbackr;/s#<(@A#<(@A ./// ./// [)I 9i000 )[(;;;;;r)F)rpeeweerr7r;rrrr=sV&<&<&<&rs7          r defence360agent/migrations/__pycache__/102_replace_comodo.cpython-311.pyc0000644000000000000000000000151300000000000023171 0ustar r_jddZddZdS)Fc Z|d|ddS)Nz~UPDATE incident SET name=replace(name, 'COMODO WAF', 'IM360 WAF'), description=replace(description, 'COMODO WAF', 'IM360 WAF')zGUPDATE disabled_rules SET name=replace(name, 'COMODO WAF', 'IM360 WAF'))sqlmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/102_replace_comodo.pymigrater sE LL F  LL <c dS)Nrs r rollbackr sDr N)F)r rr r r rs7          r defence360agent/migrations/__pycache__/103_remove_vd_license.cpython-311.opt-1.pyc0000644000000000000000000000124500000000000024650 0ustar r_j<dZddlmZeeZddZddZdS)z' Remove Virusdie registration from CLN ) getLoggerFc dSNmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/103_remove_vd_license.pymigrater Dc dSrrrs r rollbackrrrN)F)__doc__loggingr__name__loggerr rrrr rsf 8            rdefence360agent/migrations/__pycache__/103_remove_vd_license.cpython-311.pyc0000644000000000000000000000124500000000000023711 0ustar r_j<dZddlmZeeZddZddZdS)z' Remove Virusdie registration from CLN ) getLoggerFc dSNmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/103_remove_vd_license.pymigrater Dc dSrrrs r rollbackrrrN)F)__doc__loggingr__name__loggerr rrrr rsf 8            rdefence360agent/migrations/__pycache__/104_add_feature_management_permissions.cpython-311.opt-1.pyc0000644000000000000000000000274300000000000030257 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) BooleanField CharFieldModelcXeZdZGddZedZedZdS)FeatureManagementPermsceZdZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__db_tablev/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/104_add_feature_management_permissions.pyMetar s3rrT)unique)defaultN)r r r rruserr proactiverrrrrs\44444444 9D ! ! !D T***IIIrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s 011111rc J|jd}||dS)Nr )orm remove_model)rrrrrs rrollbackr!s+%\*JK 011111rN)F)peeweerrrrrr!rrrr#s1111111111+++++U+++2222222222rdefence360agent/migrations/__pycache__/104_add_feature_management_permissions.cpython-311.pyc0000644000000000000000000000274300000000000027320 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) BooleanField CharFieldModelcXeZdZGddZedZedZdS)FeatureManagementPermsceZdZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__db_tablev/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/104_add_feature_management_permissions.pyMetar s3rrT)unique)defaultN)r r r rruserr proactiverrrrrs\44444444 9D ! ! !D T***IIIrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s 011111rc J|jd}||dS)Nr )orm remove_model)rrrrrs rrollbackr!s+%\*JK 011111rN)F)peeweerrrrrr!rrrr#s1111111111+++++U+++2222222222r././@LongLink0000644000000000000000000000016100000000000007771 Lustar defence360agent/migrations/__pycache__/105_populate_default_feature_management_permissions.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/105_populate_default_feature_management_permissions.cpython-30000644000000000000000000000142600000000000031167 0ustar r_j*dZddZddZdS)Fc ^|rdS|jd}|tdS)Nfeature_management_permissions)user)orm get_or_createDEFAULT)migratordatabasefakekwargsFeatureManagementPermss /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/105_populate_default_feature_management_permissions.pymigraters: %\*JK((g(66666c dS)N)r r r r s rrollbackr sDrN)F)rrrrrrrs< 7777      r././@LongLink0000644000000000000000000000015300000000000007772 Lustar defence360agent/migrations/__pycache__/105_populate_default_feature_management_permissions.cpython-311.pycdefence360agent/migrations/__pycache__/105_populate_default_feature_management_permissions.cpython-30000644000000000000000000000142600000000000031167 0ustar r_j*dZddZddZdS)Fc ^|rdS|jd}|tdS)Nfeature_management_permissions)user)orm get_or_createDEFAULT)migratordatabasefakekwargsFeatureManagementPermss /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/105_populate_default_feature_management_permissions.pymigraters: %\*JK((g(66666c dS)N)r r r r s rrollbackr sDrN)F)rrrrrrrs< 7777      rdefence360agent/migrations/__pycache__/106_add_malware_cleanup_in_config.cpython-311.opt-1.pyc0000644000000000000000000000225500000000000027147 0ustar r_j/"ddlmZddZddZdS)) ConfigFileFc |rdSt}|}|sdS|di}|dd|dd||ddS)NMALWARE_CLEANUPtrim_file_instead_of_removalTkeep_original_files_daysF)validate)rconfig_to_dict setdefaultdict_to_config)migratordatabasefakekwargs config_fileconfmalware_cleanups q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_add_malware_cleanup_in_config.pymigraters ,,K  % % ' 'D oo&7<>>te44444c dS)N)r rrrs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrsI777777 5 5 5 5      rdefence360agent/migrations/__pycache__/106_add_malware_cleanup_in_config.cpython-311.pyc0000644000000000000000000000225500000000000026210 0ustar r_j/"ddlmZddZddZdS)) ConfigFileFc |rdSt}|}|sdS|di}|dd|dd||ddS)NMALWARE_CLEANUPtrim_file_instead_of_removalTkeep_original_files_daysF)validate)rconfig_to_dict setdefaultdict_to_config)migratordatabasefakekwargs config_fileconfmalware_cleanups q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_add_malware_cleanup_in_config.pymigraters ,,K  % % ' 'D oo&7<>>te44444c dS)N)r rrrs rrollbackrsDrN)F) defence360agent.contracts.configrrrrrrrsI777777 5 5 5 5      rdefence360agent/migrations/__pycache__/106_malware_hit_status_field_add.cpython-311.opt-1.pyc0000644000000000000000000000256300000000000027041 0ustar r_jddlZddlmZmZddlmZejdddZeje Z d dZ d d Z dS) N) CharField FloatField)importerzimav.malwarelib.configMalwareHitStatus)modulenamedefaultFc |jd}||ttjt ddS)N malware_hits)r T)null)status cleaned_at)orm add_fieldsrrFOUNDrmigratordatabasefakekwargs MalwareHits p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/106_malware_hit_status_field_add.pymigratersXn-J !1!78884(((c N|jd}||dddS)Nr r r)r remove_fieldsrs rrollbackrs,n-J :x>>>>>r)F) loggingpeeweerrdefence360agent.utilsrgetr getLogger__name__loggerrrrrr&s((((((((******8< #*>>>>r)F) loggingpeeweerrdefence360agent.utilsrgetr getLogger__name__loggerrrrrr&s((((((((******8< #*>>>> <<<:;;;;;;r$si777777$  8 $ $<<<<*      rdefence360agent/migrations/__pycache__/107_add_bruteforce_rule_33339.cpython-311.pyc0000644000000000000000000000267600000000000025001 0ustar r_j4NddlZddlmZdZejeZddZddZdS)N) ConfigFileMOD_SEC_BLOCK_BY_CUSTOM_RULEFc ,|rdS t}|d}|ti}ddd|d<|t|idS#t $rt dYdSwxYw)NF) normalizex ) check_period max_incidents33339zFailed to create rule for 33339)rconfig_to_dict setdefaultKEYdict_to_config Exceptionlogger exception)migratordatabasefakekwargs config_fileconfigmod_sec_block_ruless m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_add_bruteforce_rule_33339.pymigrater s  < ll ++e+<<$//R88( ( G$ ""C)<#=>>>>> <<<:;;;;;;r$si777777$  8 $ $<<<<*      rdefence360agent/migrations/__pycache__/107_malware_hit_status_field_populate.cpython-311.opt-1.pyc0000644000000000000000000000205200000000000030134 0ustar r_jJddlZddlmZejeZddZddZdS)N) BooleanFieldFc T|s%|jd}||ddSdS)N malware_hitsrestored)orm remove_fieldsmigratordatabasefakekwargs MalwareHits u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_malware_hit_status_field_populate.pymigraters< 7\.1 z:6666677c j|jd}||tddS)NrF)default)r)r add_fieldsrr s rrollbackrs8n-J  \%-H-H-HIIIIIr)F)loggingpeeweer getLogger__name__loggerrrrrrsj  8 $ $7777 JJJJJJrdefence360agent/migrations/__pycache__/107_malware_hit_status_field_populate.cpython-311.pyc0000644000000000000000000000205200000000000027175 0ustar r_jJddlZddlmZejeZddZddZdS)N) BooleanFieldFc T|s%|jd}||ddSdS)N malware_hitsrestored)orm remove_fieldsmigratordatabasefakekwargs MalwareHits u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/107_malware_hit_status_field_populate.pymigraters< 7\.1 z:6666677c j|jd}||tddS)NrF)default)r)r add_fieldsrr s rrollbackrs8n-J  \%-H-H-HIIIIIr)F)loggingpeeweer getLogger__name__loggerrrrrrsj  8 $ $7777 JJJJJJrdefence360agent/migrations/__pycache__/108_feature_management_cleanup_add.cpython-311.opt-1.pyc0000644000000000000000000000206600000000000027335 0ustar r_jJddlZddlmZejeZddZddZdS)N) BooleanFieldFc j|jd}||tddS)Nfeature_management_permissionsF)default)cleanup)orm add_fieldsrmigratordatabasefakekwargsFeatureManagementPermss r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_feature_management_cleanup_add.pymigratersG%\*JK  U(C(C(Cc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs-%\*JK 19=====r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $>>>>>>rdefence360agent/migrations/__pycache__/108_feature_management_cleanup_add.cpython-311.pyc0000644000000000000000000000206600000000000026376 0ustar r_jJddlZddlmZejeZddZddZdS)N) BooleanFieldFc j|jd}||tddS)Nfeature_management_permissionsF)default)cleanup)orm add_fieldsrmigratordatabasefakekwargsFeatureManagementPermss r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_feature_management_cleanup_add.pymigratersG%\*JK  U(C(C(Cc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs-%\*JK 19=====r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $>>>>>>rdefence360agent/migrations/__pycache__/108_validate_config.cpython-311.opt-1.pyc0000644000000000000000000000343600000000000024307 0ustar r_jZddlZddlZddlmZmZmZejeZddZ ddZ dS)N)ConfigsValidatorConfigsValidatorError LocalConfigFc |rdS tjdS#t$rzt}|jdz}t j|j||}||t d|YdSwxYw#t$rt dYdSwxYw)Nz.invalidz?Invalid config replaced with default one. Old config save in %sz1Failed to replace invalid config with default one) rvalidate_system_configrrpathosrenameconfig_to_dictdict_to_configloggerwarning Exception exception)migratordatabasefakekwargs local_config backup_configdefault_configs c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_validate_config.pymigraters N   3 5 5 5 5 5$   &==L(- :M Il' 7 7 7)88::N  ' ' 7 7 7 NN)        NNNLMMMMMMNs(BB B#B  B##$C  C c dS)N)rrrrs rrollbackr*sD)F) loggingr defence360agent.contracts.configrrr getLogger__name__r rrrrrr"s   8 $ $NNNN0      rdefence360agent/migrations/__pycache__/108_validate_config.cpython-311.pyc0000644000000000000000000000343600000000000023350 0ustar r_jZddlZddlZddlmZmZmZejeZddZ ddZ dS)N)ConfigsValidatorConfigsValidatorError LocalConfigFc |rdS tjdS#t$rzt}|jdz}t j|j||}||t d|YdSwxYw#t$rt dYdSwxYw)Nz.invalidz?Invalid config replaced with default one. Old config save in %sz1Failed to replace invalid config with default one) rvalidate_system_configrrpathosrenameconfig_to_dictdict_to_configloggerwarning Exception exception)migratordatabasefakekwargs local_config backup_configdefault_configs c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/108_validate_config.pymigraters N   3 5 5 5 5 5$   &==L(- :M Il' 7 7 7)88::N  ' ' 7 7 7 NN)        NNNLMMMMMMNs(BB B#B  B##$C  C c dS)N)rrrrs rrollbackr*sD)F) loggingr defence360agent.contracts.configrrr getLogger__name__r rrrrrr"s   8 $ $NNNN0      rdefence360agent/migrations/__pycache__/109_dos_detector.cpython-311.opt-1.pyc0000644000000000000000000000337600000000000023653 0ustar r_jNddlZddlmZmZejeZddZddZdS)N)_DOS_DETECTOR_MIN_LIMIT ConfigFileFc |rdS t}|d}d|vrdSd|dvrTt|ddtr3t |ddt |dd<|dd=d|dvr |dd|dd<|dd=||ddS#t$rt d YdSwxYw) NFDOSmax_connections default_limittimeoutintervalT) overwritezFailed to replace DOS settings) rconfig_to_dict isinstanceintmaxrdict_to_config Exceptionlogger exception)migratordatabasefakekwargs config_fileconfigs `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/109_dos_detector.pymigrater s0 ; ll ++E22    F u - -* 5M+ ,c3 3 -.1u /02I..F5M/ *u /0 u % %(.u i(@F5M* %u i(""6T"::::: ;;;9::::::;s'CBC$C87C8c dS)N)rrrrs rrollbackr'sD)F) logging defence360agent.contracts.configrr getLogger__name__rrrrrrr$s|  8 $ $;;;;8      rdefence360agent/migrations/__pycache__/109_dos_detector.cpython-311.pyc0000644000000000000000000000337600000000000022714 0ustar r_jNddlZddlmZmZejeZddZddZdS)N)_DOS_DETECTOR_MIN_LIMIT ConfigFileFc |rdS t}|d}d|vrdSd|dvrTt|ddtr3t |ddt |dd<|dd=d|dvr |dd|dd<|dd=||ddS#t$rt d YdSwxYw) NFDOSmax_connections default_limittimeoutintervalT) overwritezFailed to replace DOS settings) rconfig_to_dict isinstanceintmaxrdict_to_config Exceptionlogger exception)migratordatabasefakekwargs config_fileconfigs `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/109_dos_detector.pymigrater s0 ; ll ++E22    F u - -* 5M+ ,c3 3 -.1u /02I..F5M/ *u /0 u % %(.u i(@F5M* %u i(""6T"::::: ;;;9::::::;s'CBC$C87C8c dS)N)rrrrs rrollbackr'sD)F) logging defence360agent.contracts.configrr getLogger__name__rrrrrrr$s|  8 $ $;;;;8      rdefence360agent/migrations/__pycache__/110_ignore_list_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000311700000000000025521 0ustar r_jaddlZddZddZdS)NFc h|rdSGddtj}||dS)NceZdZejdZejdZejdZejdZ GddZ dS)migrate..IgnoreListNewF)nullc6eZdZdZejdddZdS)#migrate..IgnoreListNew.Metaignore_list_newnetwork_addressnetmaskversionN)__name__ __module__ __qualname__db_tablepw CompositeKey primary_keyi/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/110_ignore_list_ip_as_int.pyMetars-(H)"/!9iKKKrrN) r rrr CharFieldip IntegerFieldr r r rrrr IgnoreListNewrs R\u % % %)"/u555!"/u---!"/u---          rr)rModel create_model)migratordatabasefakekwargsrs rmigrater"sW          -(((((rc dS)Nr)rrr r!s rrollbackr$sDr)F)peeweerr"r$rrrr&sC))))&      rdefence360agent/migrations/__pycache__/110_ignore_list_ip_as_int.cpython-311.pyc0000644000000000000000000000311700000000000024562 0ustar r_jaddlZddZddZdS)NFc h|rdSGddtj}||dS)NceZdZejdZejdZejdZejdZ GddZ dS)migrate..IgnoreListNewF)nullc6eZdZdZejdddZdS)#migrate..IgnoreListNew.Metaignore_list_newnetwork_addressnetmaskversionN)__name__ __module__ __qualname__db_tablepw CompositeKey primary_keyi/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/110_ignore_list_ip_as_int.pyMetars-(H)"/!9iKKKrrN) r rrr CharFieldip IntegerFieldr r r rrrr IgnoreListNewrs R\u % % %)"/u555!"/u---!"/u---          rr)rModel create_model)migratordatabasefakekwargsrs rmigrater"sW          -(((((rc dS)Nr)rrr r!s rrollbackr$sDr)F)peeweerr"r$rrrr&sC))))&      rdefence360agent/migrations/__pycache__/111_ignore_list_ip_as_int.cpython-311.opt-1.pyc0000644000000000000000000000462600000000000025530 0ustar r_j-ddlZddZddZdS)NFc |rdS|jd}|jd} ddlm}ddlm}|5d|D}t} |D]=} tj | } n#t$rY$wxYw| | >| D]=} || \} } }| || | | |> dddn #1swxYwYn#t$rYnwxYw|d|d dS) Nignore_list_new ignore_listr)IP)pack_ip_networkcg|] }|d S)ip).0items i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/111_ignore_list_ip_as_int.py zmigrate..sMMM$t*MMM)r network_addressnetmaskversionzDROP TABLE ignore_listz1ALTER TABLE ignore_list_new RENAME TO ignore_list)ormdefence360agent.utils.validaterim360.utils.netratomicselectdictsset ipaddress ip_network ValueErroraddcreateip_net_to_string ImportErrorsql)migratordatabasefakekwargs IgnoreListNew IgnoreListrr ip_stringsipsr r netmaskrs r migrater,s L!23Mm,J555555333333__    MM1B1B1D1D1J1J1L1LMMMJ%%C"  "-d33BB!H   %4_R%8%8"T7$$**2..$' # %                      . LL)*** LLDEEEEEsI DAD BD  B&#D %B&&AD  DD D%$D%c dS)z$Write your rollback migrations here.Nr )r"r#r$r%s r rollbackr.(sDr)F)rr,r.r rr r/sH!F!F!F!FH      rdefence360agent/migrations/__pycache__/111_ignore_list_ip_as_int.cpython-311.pyc0000644000000000000000000000462600000000000024571 0ustar r_j-ddlZddZddZdS)NFc |rdS|jd}|jd} ddlm}ddlm}|5d|D}t} |D]=} tj | } n#t$rY$wxYw| | >| D]=} || \} } }| || | | |> dddn #1swxYwYn#t$rYnwxYw|d|d dS) Nignore_list_new ignore_listr)IP)pack_ip_networkcg|] }|d S)ip).0items i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/111_ignore_list_ip_as_int.py zmigrate..sMMM$t*MMM)r network_addressnetmaskversionzDROP TABLE ignore_listz1ALTER TABLE ignore_list_new RENAME TO ignore_list)ormdefence360agent.utils.validaterim360.utils.netratomicselectdictsset ipaddress ip_network ValueErroraddcreateip_net_to_string ImportErrorsql)migratordatabasefakekwargs IgnoreListNew IgnoreListrr ip_stringsipsr r netmaskrs r migrater,s L!23Mm,J555555333333__    MM1B1B1D1D1J1J1L1LMMMJ%%C"  "-d33BB!H   %4_R%8%8"T7$$**2..$' # %                      . LL)*** LLDEEEEEsI DAD BD  B&#D %B&&AD  DD D%$D%c dS)z$Write your rollback migrations here.Nr )r"r#r$r%s r rollbackr.(sDr)F)rr,r.r rr r/sH!F!F!F!FH      rdefence360agent/migrations/__pycache__/112_hardened_php.cpython-311.opt-1.pyc0000644000000000000000000000721200000000000023601 0ustar r_j\ddlZddlZddlZddlZddlmZejdddZejdddZej e Z dZ dZ d Zd Zd Zd ZddZddZdS)N)importerzimav.malwarelib.utils.chattrsubtract_flags)modulenamedefaultFS_IMMUTABLE_FLzimunify360-alt-php.repozimunify360-ea-php-hardened.repoz/etc/yum.repos.d/cd|vrtthStjdrt tgSt tgS)N cloudlinuxz/usr/local/cpanel/cpanel)ALT_PHPEA_PHPospathexistsset)releases `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/112_hardened_php.pyirrelevant_reposrsMw  2 3 3G9~~F8}}cxtdSttfD]}t|z}tj|s,t|5}t|tt j |ddddn #1swxYwYdS)Ni) rr r REPOS_DIRr rropenfilenorchmod) repo_namerfs rfix_permissionsr"sv&(( 9$w~~d##   $ZZ (1 188:: 7 7 7 HQXXZZ ' ' ' ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ((sAB..B2 5B2 ctjdsdStd5}|}dddn #1swxYwYt t|D]O}tj t5tj t|zdddn #1swxYwYPdS)Nz/etc/redhat-release) r rrrreadlowerrr contextlibsuppressFileNotFoundErrorunlinkr)rrrs r do_migrater$/s@ 7>>/ 0 0 # $ $#&&((..""###############%g..--  !2 3 3 - - Ii)+ , , , - - - - - - - - - - - - - - ---s#'A$$A(+A('CC C Fc |rdS tdS#t$rtdYdSwxYw)Nz+Failed to clean up HardenedPHP repositories)r$ Exceptionlogger exceptionmigratordatabasefakekwargss rmigrater.;s_ H HHHFGGGGGGHs $>>c dS)Nr)s rrollbackr1DsDr)F)r loggingr os.pathdefence360agent.utilsrgetrr getLogger__name__r'r r rrrr$r.r1r0rrr8s ****** )0@$(, )0A4  8 $ $ # *      ( ( ( - - -HHHH      rdefence360agent/migrations/__pycache__/112_hardened_php.cpython-311.pyc0000644000000000000000000000721200000000000022642 0ustar r_j\ddlZddlZddlZddlZddlmZejdddZejdddZej e Z dZ dZ d Zd Zd Zd ZddZddZdS)N)importerzimav.malwarelib.utils.chattrsubtract_flags)modulenamedefaultFS_IMMUTABLE_FLzimunify360-alt-php.repozimunify360-ea-php-hardened.repoz/etc/yum.repos.d/cd|vrtthStjdrt tgSt tgS)N cloudlinuxz/usr/local/cpanel/cpanel)ALT_PHPEA_PHPospathexistsset)releases `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/112_hardened_php.pyirrelevant_reposrsMw  2 3 3G9~~F8}}cxtdSttfD]}t|z}tj|s,t|5}t|tt j |ddddn #1swxYwYdS)Ni) rr r REPOS_DIRr rropenfilenorchmod) repo_namerfs rfix_permissionsr"sv&(( 9$w~~d##   $ZZ (1 188:: 7 7 7 HQXXZZ ' ' ' ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ((sAB..B2 5B2 ctjdsdStd5}|}dddn #1swxYwYt t|D]O}tj t5tj t|zdddn #1swxYwYPdS)Nz/etc/redhat-release) r rrrreadlowerrr contextlibsuppressFileNotFoundErrorunlinkr)rrrs r do_migrater$/s@ 7>>/ 0 0 # $ $#&&((..""###############%g..--  !2 3 3 - - Ii)+ , , , - - - - - - - - - - - - - - ---s#'A$$A(+A('CC C Fc |rdS tdS#t$rtdYdSwxYw)Nz+Failed to clean up HardenedPHP repositories)r$ Exceptionlogger exceptionmigratordatabasefakekwargss rmigrater.;s_ H HHHFGGGGGGHs $>>c dS)Nr)s rrollbackr1DsDr)F)r loggingr os.pathdefence360agent.utilsrgetrr getLogger__name__r'r r rrrr$r.r1r0rrr8s ****** )0@$(, )0A4  8 $ $ # *      ( ( ( - - -HHHH      rdefence360agent/migrations/__pycache__/113_move_quarantined_files.cpython-311.opt-1.pyc0000644000000000000000000000077000000000000025706 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/113_move_quarantined_files.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/113_move_quarantined_files.cpython-311.pyc0000644000000000000000000000077000000000000024747 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/113_move_quarantined_files.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/114_disable_auto-quarantine.cpython-311.opt-1.pyc0000644000000000000000000000345500000000000025767 0ustar r_jVddlZddlZddlmZmZejeZddZddZ dS)N) ConfigFileCoreFc |rdSdg}tjtjr1|tjtj|D]}t|}|}|s)| di d}|dkr5d|dd< | |dd }#t$rYwxYwdS) zWrite your migrations here.N)usernameMALWARE_SCANNINGdefault_action quarantinenotifyTF) overwritevalidate) ospathexistsr USER_CONFDIRextendlistdirrconfig_to_dict setdefaultgetdict_to_config Exception) migratordatabasefakekwargs usernamesr config_fileconfigrs k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/114_disable_auto-quarantine.pymigrater s1 I w~~d'((8D$566777 (333 ++--  **+=rBBFF    \ ) );CF% &'7 8 **dU+     *sC!! C.-C.c dS)N)rrrrs rrollbackr#"sD)F) loggingr defence360agent.contracts.configrr getLogger__name__loggerr r#r"r$rr*ss ========  8 $ $2      r$defence360agent/migrations/__pycache__/114_disable_auto-quarantine.cpython-311.pyc0000644000000000000000000000345500000000000025030 0ustar r_jVddlZddlZddlmZmZejeZddZddZ dS)N) ConfigFileCoreFc |rdSdg}tjtjr1|tjtj|D]}t|}|}|s)| di d}|dkr5d|dd< | |dd }#t$rYwxYwdS) zWrite your migrations here.N)usernameMALWARE_SCANNINGdefault_action quarantinenotifyTF) overwritevalidate) ospathexistsr USER_CONFDIRextendlistdirrconfig_to_dict setdefaultgetdict_to_config Exception) migratordatabasefakekwargs usernamesr config_fileconfigrs k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/114_disable_auto-quarantine.pymigrater s1 I w~~d'((8D$566777 (333 ++--  **+=rBBFF    \ ) );CF% &'7 8 **dU+     *sC!! C.-C.c dS)N)rrrrs rrollbackr#"sD)F) loggingr defence360agent.contracts.configrr getLogger__name__loggerr r#r"r$rr*ss ========  8 $ $2      r$defence360agent/migrations/__pycache__/115_feature_management_fields.cpython-311.opt-1.pyc0000644000000000000000000000426200000000000026342 0ustar r_jN2ddlZddlmZmZmZddZddZdS)N)NAFULL AV_REPORTFc |jd}||tjtdtjdttgtjtdtjdtttg| dtf| dtf| d tf| d tf| |d d dS) Nfeature_management_permissionsFzproactive_new in ('{}','{}'))defaultnull constraintszav in ('{}','{}','{}')) proactive_newavz>UPDATE feature_management_permissions SET av=? WHERE cleanup=1z>UPDATE feature_management_permissions SET av=? WHERE cleanup=0zKUPDATE feature_management_permissions SET proactive_new=? WHERE proactive=1zKUPDATE feature_management_permissions SET proactive_new=? WHERE proactive=0cleanup proactive) orm add_fieldspw TextFieldrCheckformatrrsql remove_fields)migratordatabasefakekwargspermissions_models m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/115_feature_management_fields.pymigratersS %EF l7>>r4HHII    <188YMMNN   $ LLH  LLH    LL    LL    ,iEEEEEc dS)N)rrrrs rrollbackr!1sDr)F)peeweer,defence360agent.feature_management.constantsrrrrr!r rrr$sfLLLLLLLLLL(F(F(F(FV      rdefence360agent/migrations/__pycache__/115_feature_management_fields.cpython-311.pyc0000644000000000000000000000426200000000000025403 0ustar r_jN2ddlZddlmZmZmZddZddZdS)N)NAFULL AV_REPORTFc |jd}||tjtdtjdttgtjtdtjdtttg| dtf| dtf| d tf| d tf| |d d dS) Nfeature_management_permissionsFzproactive_new in ('{}','{}'))defaultnull constraintszav in ('{}','{}','{}')) proactive_newavz>UPDATE feature_management_permissions SET av=? WHERE cleanup=1z>UPDATE feature_management_permissions SET av=? WHERE cleanup=0zKUPDATE feature_management_permissions SET proactive_new=? WHERE proactive=1zKUPDATE feature_management_permissions SET proactive_new=? WHERE proactive=0cleanup proactive) orm add_fieldspw TextFieldrCheckformatrrsql remove_fields)migratordatabasefakekwargspermissions_models m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/115_feature_management_fields.pymigratersS %EF l7>>r4HHII    <188YMMNN   $ LLH  LLH    LL    LL    ,iEEEEEc dS)N)rrrrs rrollbackr!1sDr)F)peeweer,defence360agent.feature_management.constantsrrrrr!r rrr$sfLLLLLLLLLL(F(F(F(FV      rdefence360agent/migrations/__pycache__/116_feature_management_fields.cpython-311.opt-1.pyc0000644000000000000000000000153300000000000026341 0ustar r_jddZddZdS)Fc N|jd}||dddS)z{ This is final accions for migration 115. For some reason, it does not work if executed in the same migration feature_management_permissions proactive_new proactiveN)orm rename_field)migratordatabasefakekwargspermissions_models m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/116_feature_management_fields.pymigraters1 ! %EF +_kJJJJJc dS)N)rr r r s r rollbackr sDrN)F)rrrrr rs;KKKK      rdefence360agent/migrations/__pycache__/116_feature_management_fields.cpython-311.pyc0000644000000000000000000000153300000000000025402 0ustar r_jddZddZdS)Fc N|jd}||dddS)z{ This is final accions for migration 115. For some reason, it does not work if executed in the same migration feature_management_permissions proactive_new proactiveN)orm rename_field)migratordatabasefakekwargspermissions_models m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/116_feature_management_fields.pymigraters1 ! %EF +_kJJJJJc dS)N)rr r r s r rollbackr sDrN)F)rrrrr rs;KKKK      rdefence360agent/migrations/__pycache__/117_remove_incorrect_fields.cpython-311.opt-1.pyc0000644000000000000000000000321700000000000026061 0ustar r_jnddlZddlmZmZejeZdZdefdefdZddZ dS) N)IConfig LocalConfigc6 |d}d|vrdS|ddd|ddd||dddS#t$rtdYdSwxYw) NF) normalizeDOStimeoutmax_connectionsT) overwritevalidatezFailed to remove fields)config_to_dictpopdict_to_config Exceptionlogger exception) config_fileconfigs k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/117_remove_incorrect_fields.py _fix_configrs 4++e+<<    Fu )T***u +T222""6TE"JJJJJ 44423333334sA0AA00$BBFrc ,|rdSt|dSN)r)migratordatabasefakerkwargss rmigraters%  c dSr)rrrrs rrollbackr $sDr)F) logging defence360agent.contracts.configrr getLogger__name__rrrr rrrr%sAAAAAAAA  8 $ $ 4 4 4$ &;==            rdefence360agent/migrations/__pycache__/117_remove_incorrect_fields.cpython-311.pyc0000644000000000000000000000321700000000000025122 0ustar r_jnddlZddlmZmZejeZdZdefdefdZddZ dS) N)IConfig LocalConfigc6 |d}d|vrdS|ddd|ddd||dddS#t$rtdYdSwxYw) NF) normalizeDOStimeoutmax_connectionsT) overwritevalidatezFailed to remove fields)config_to_dictpopdict_to_config Exceptionlogger exception) config_fileconfigs k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/117_remove_incorrect_fields.py _fix_configrs 4++e+<<    Fu )T***u +T222""6TE"JJJJJ 44423333334sA0AA00$BBFrc ,|rdSt|dSN)r)migratordatabasefakerkwargss rmigraters%  c dSr)rrrrs rrollbackr $sDr)F) logging defence360agent.contracts.configrr getLogger__name__rrrr rrrr%sAAAAAAAA  8 $ $ 4 4 4$ &;==            rdefence360agent/migrations/__pycache__/118_add_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000076400000000000026332 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_add_malware_user_infected.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/118_add_malware_user_infected.cpython-311.pyc0000644000000000000000000000076400000000000025373 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_add_malware_user_infected.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/118_remove_country_subnets.cpython-311.opt-1.pyc0000644000000000000000000000141500000000000026010 0ustar r_j>ddlZejeZddZddZdS)NFc J|jd}||dS)Ncountry_subnets)orm remove_model)migratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_remove_country_subnets.pymigrater s)\"34N .)))))c dS)N)rrr r s r rollbackr sDr)F)logging getLogger__name__loggerr rrrr rsR  8 $ $****       rdefence360agent/migrations/__pycache__/118_remove_country_subnets.cpython-311.pyc0000644000000000000000000000141500000000000025051 0ustar r_j>ddlZejeZddZddZdS)NFc J|jd}||dS)Ncountry_subnets)orm remove_model)migratordatabasefakekwargsCountrySubnetss j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/118_remove_country_subnets.pymigrater s)\"34N .)))))c dS)N)rrr r s r rollbackr sDr)F)logging getLogger__name__loggerr rrrr rsR  8 $ $****       rdefence360agent/migrations/__pycache__/119_populate_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000077100000000000027432 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/119_populate_malware_user_infected.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/119_populate_malware_user_infected.cpython-311.pyc0000644000000000000000000000077100000000000026473 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/119_populate_malware_user_infected.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/120_scheduled_scan.cpython-311.opt-1.pyc0000644000000000000000000000460400000000000024125 0ustar r_jddlZddlmZmZddlZddlmZddlm Z e j dddZ ej e Ze je je je jfZd dZd d Zd d ZdS)N)date timedelta) ConfigFile)importerzimav.malwarelib.configMalwareScanType)modulenamedefaultctjtdz}dd|jii} t |}||dS#t $rtdYdSwxYw)N)daysMALWARE_SCAN_SCHEDULE day_of_month)pathz*Failed to set malware scan schedule config) rtodayrdayrdict_to_config Exceptionlogger exception)rtomorrowconfig config_files b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/120_scheduled_scan.py_update_configrsz||iQ////H HL" F G d+++ ""6***** GGGEFFFFFFGs%A$B?BFc |jd}||tjdtjdt g|rdStdS)N malware_scansFz type in {})null constraints)type)orm change_fieldspw CharFieldCheckformattypesr)migratordatabasefakekwargs MalwareScans rmigrater-'s,/K  \RXl.A.A%.H.H%I%I$J    c dSN)r(r)r*r+s rrollbackr27sDr.r0)F)loggingdatetimerrpeeweer# defence360agent.contracts.configrdefence360agent.utilsrgetr getLogger__name__r ON_DEMANDREALTIMEMALWARE_RESPONSE BACKGROUNDr'rr-r2r1r.rr?s$$$$$$$$777777******(, #*;T  8 $ $$   G G G G           r.defence360agent/migrations/__pycache__/120_scheduled_scan.cpython-311.pyc0000644000000000000000000000460400000000000023166 0ustar r_jddlZddlmZmZddlZddlmZddlm Z e j dddZ ej e Ze je je je jfZd dZd d Zd d ZdS)N)date timedelta) ConfigFile)importerzimav.malwarelib.configMalwareScanType)modulenamedefaultctjtdz}dd|jii} t |}||dS#t $rtdYdSwxYw)N)daysMALWARE_SCAN_SCHEDULE day_of_month)pathz*Failed to set malware scan schedule config) rtodayrdayrdict_to_config Exceptionlogger exception)rtomorrowconfig config_files b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/120_scheduled_scan.py_update_configrsz||iQ////H HL" F G d+++ ""6***** GGGEFFFFFFGs%A$B?BFc |jd}||tjdtjdt g|rdStdS)N malware_scansFz type in {})null constraints)type)orm change_fieldspw CharFieldCheckformattypesr)migratordatabasefakekwargs MalwareScans rmigrater-'s,/K  \RXl.A.A%.H.H%I%I$J    c dSN)r(r)r*r+s rrollbackr27sDr.r0)F)loggingdatetimerrpeeweer# defence360agent.contracts.configrdefence360agent.utilsrgetr getLogger__name__r ON_DEMANDREALTIMEMALWARE_RESPONSE BACKGROUNDr'rr-r2r1r.rr?s$$$$$$$$777777******(, #*;T  8 $ $$   G G G G           r.defence360agent/migrations/__pycache__/121_drop_captcha_stat.cpython-311.opt-1.pyc0000644000000000000000000000115300000000000024640 0ustar r_jddZddZdS)Fc F||jddS)N captcha_stat) remove_modelormmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/121_drop_captcha_stat.pymigrater s# (,~677777c dS)Nrs r rollbackrsDr N)F)r rrr r rs78888      r defence360agent/migrations/__pycache__/121_drop_captcha_stat.cpython-311.pyc0000644000000000000000000000115300000000000023701 0ustar r_jddZddZdS)Fc F||jddS)N captcha_stat) remove_modelormmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/121_drop_captcha_stat.pymigrater s# (,~677777c dS)Nrs r rollbackrsDr N)F)r rrr r rs78888      r defence360agent/migrations/__pycache__/122_cagefs_unmount.cpython-311.opt-1.pyc0000644000000000000000000000076200000000000024201 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/122_cagefs_unmount.pymigrater s  Dc dSrrrs r rollbackrsDr N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/122_cagefs_unmount.cpython-311.pyc0000644000000000000000000000076200000000000023242 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/122_cagefs_unmount.pymigrater s  Dc dSrrrs r rollbackrsDr N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/123_add_last_user_scan.cpython-311.opt-1.pyc0000644000000000000000000000236400000000000025002 0ustar r_jDddlZGddejZddZddZdS)NceZdZGddZejdZejdZejdZ dS) LastUserScanceZdZdZdS)LastUserScan.Metalast_user_scansN)__name__ __module__ __qualname__db_tablef/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_add_last_user_scan.pyMetars$r rT) primary_keyF)nullN) rr r rpw CharField last_scanid IntegerFieldstarteduidr r rrrst%%%%%%%%",4000Kbo5)))G "/u % % %CCCr rFc dSNr migratordatabasefakekwargss rmigrater Dr c dSrr rs rrollbackr"r r )F)peeweerModelrrr"r r rr%so&&&&&28&&&          r defence360agent/migrations/__pycache__/123_add_last_user_scan.cpython-311.pyc0000644000000000000000000000236400000000000024043 0ustar r_jDddlZGddejZddZddZdS)NceZdZGddZejdZejdZejdZ dS) LastUserScanceZdZdZdS)LastUserScan.Metalast_user_scansN)__name__ __module__ __qualname__db_tablef/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_add_last_user_scan.pyMetars$r rT) primary_keyF)nullN) rr r rpw CharField last_scanid IntegerFieldstarteduidr r rrrst%%%%%%%%",4000Kbo5)))G "/u % % %CCCr rFc dSNr migratordatabasefakekwargss rmigrater Dr c dSrr rs rrollbackr"r r )F)peeweerModelrrr"r r rr%so&&&&&28&&&          r defence360agent/migrations/__pycache__/123_disable_scheduled_scan.cpython-311.opt-1.pyc0000644000000000000000000000357400000000000025620 0ustar r_jtddlZddlZddlZddlmZmZejeZdZ d dZ e fdZ d dZ d dZ dS) N) ConfigFileNONEz!/etc/cron.d/imunify_scan_schedulecddtii} t|}||dS#t$rtdYdSwxYw)NMALWARE_SCAN_SCHEDULEintervalpathz*Failed to set malware scan schedule config)rrdict_to_config Exceptionlogger exception)r config config_files j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_disable_scheduled_scan.py_update_configr s " F G d+++ ""6***** GGGEFFFFFFGs%4$AActjt5tj|ddddS#1swxYwYdSN) contextlibsuppressFileNotFoundErrorosunlinkrs r _remove_cronrs  . / / $s<AAFc dSrmigratordatabasefakekwargss rmigrater! s  Dc dSrrrs rrollbackr$*sDr"r)F)rloggingr defence360agent.contracts.configrr getLogger__name__r CRON_PATHrrr!r$rr"rr*s ========  8 $ $ 0  G G G G            r"defence360agent/migrations/__pycache__/123_disable_scheduled_scan.cpython-311.pyc0000644000000000000000000000357400000000000024661 0ustar r_jtddlZddlZddlZddlmZmZejeZdZ d dZ e fdZ d dZ d dZ dS) N) ConfigFileNONEz!/etc/cron.d/imunify_scan_schedulecddtii} t|}||dS#t$rtdYdSwxYw)NMALWARE_SCAN_SCHEDULEintervalpathz*Failed to set malware scan schedule config)rrdict_to_config Exceptionlogger exception)r config config_files j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_disable_scheduled_scan.py_update_configr s " F G d+++ ""6***** GGGEFFFFFFGs%4$AActjt5tj|ddddS#1swxYwYdSN) contextlibsuppressFileNotFoundErrorosunlinkrs r _remove_cronrs  . / / $s<AAFc dSrmigratordatabasefakekwargss rmigrater! s  Dc dSrrrs rrollbackr$*sDr"r)F)rloggingr defence360agent.contracts.configrr getLogger__name__r CRON_PATHrrr!r$rr"rr*s ========  8 $ $ 0  G G G G            r"defence360agent/migrations/__pycache__/123_rename_plesk_vendor.cpython-311.opt-1.pyc0000644000000000000000000000365500000000000025213 0ustar r_jhddlZddlmZmZejeZejddZddZ dS)N)run_coroantivirus_modeFc |rdS ddlm}ddlm}n#t$rYdSwxYw |r|t |rg|}t |}dd|vr't | dSdSdSdS#t$r&}t d|Yd}~dSd}~wwxYw)Nr)Plesk)plesk_supports_custom_vendors imunify360 z/Unable to reinstall modsec "custom" ruleset: %s) im360.subsys.panels.pleskr&im360.subsys.panels.plesk.mod_securityr ImportError is_installedrmodsec_vendor_listjoininstall_settings Exceptionloggerwarning) migratordatabasefakekwargsrrpanelinstalled_vendorses g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_rename_plesk_vendor.pymigrater sc 333333        M      3H-J-J-L-L$M$M 3EGGE ()A)A)C)C D D sxx(9:::://1122222  3 3 3 3;: MMMH!LLLLLLLLLMs$  !!BB;; C+C&&C+c dS)N)rrrrs rrollbackr sD)F) loggingdefence360agent.utilsrr getLogger__name__rskiprrrr rr&s::::::::  8 $ $MMMM,      r defence360agent/migrations/__pycache__/123_rename_plesk_vendor.cpython-311.pyc0000644000000000000000000000365500000000000024254 0ustar r_jhddlZddlmZmZejeZejddZddZ dS)N)run_coroantivirus_modeFc |rdS ddlm}ddlm}n#t$rYdSwxYw |r|t |rg|}t |}dd|vr't | dSdSdSdS#t$r&}t d|Yd}~dSd}~wwxYw)Nr)Plesk)plesk_supports_custom_vendors imunify360 z/Unable to reinstall modsec "custom" ruleset: %s) im360.subsys.panels.pleskr&im360.subsys.panels.plesk.mod_securityr ImportError is_installedrmodsec_vendor_listjoininstall_settings Exceptionloggerwarning) migratordatabasefakekwargsrrpanelinstalled_vendorses g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/123_rename_plesk_vendor.pymigrater sc 333333        M      3H-J-J-L-L$M$M 3EGGE ()A)A)C)C D D sxx(9:::://1122222  3 3 3 3;: MMMH!LLLLLLLLLMs$  !!BB;; C+C&&C+c dS)N)rrrrs rrollbackr sD)F) loggingdefence360agent.utilsrr getLogger__name__rskiprrrr rr&s::::::::  8 $ $MMMM,      r defence360agent/migrations/__pycache__/124_add_hook_management_functionality.cpython-311.opt-1.pyc0000644000000000000000000000357700000000000030111 0ustar r_j~bddlmZddlmZmZmZmZddlmZGddeZd dZ d dZ d S) )time)Model CharField IntegerField BooleanField) FilenameFieldceZdZGddZedZedZeddZ e dZ dS) EventHookceZdZdZdS)EventHook.Meta event_hookN)__name__ __module__ __qualname__db_tableu/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_hook_management_functionality.pyMetar srrF)nullc8ttSN)intrrrrzEventHook.ss466{{r)rdefault)rN) rrrrrpathreventrcreatedrnativerrrr r s         =e $ $ $D I5 ! ! !El/B/BCCCG \% ( ( (FFFrr Fc :|tdSr) create_modelr )migratordatabasefakekwargss rmigrater&s )$$$$$rc J|jd}||dS)Nr )orm remove_model)r"r#r$r%r s rrollbackr*s( \*I )$$$$$rN)F) rpeeweerrrr$defence360agent.model.simplificationrr r&r*rrrr-s????????????>>>>>>))))))))%%%%%%%%%%rdefence360agent/migrations/__pycache__/124_add_hook_management_functionality.cpython-311.pyc0000644000000000000000000000357700000000000027152 0ustar r_j~bddlmZddlmZmZmZmZddlmZGddeZd dZ d dZ d S) )time)Model CharField IntegerField BooleanField) FilenameFieldceZdZGddZedZedZeddZ e dZ dS) EventHookceZdZdZdS)EventHook.Meta event_hookN)__name__ __module__ __qualname__db_tableu/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_hook_management_functionality.pyMetar srrF)nullc8ttSN)intrrrrzEventHook.ss466{{r)rdefault)rN) rrrrrpathreventrcreatedrnativerrrr r s         =e $ $ $D I5 ! ! !El/B/BCCCG \% ( ( (FFFrr Fc :|tdSr) create_modelr )migratordatabasefakekwargss rmigrater&s )$$$$$rc J|jd}||dS)Nr )orm remove_model)r"r#r$r%r s rrollbackr*s( \*I )$$$$$rN)F) rpeeweerrrr$defence360agent.model.simplificationrr r&r*rrrr-s????????????>>>>>>))))))))%%%%%%%%%%rdefence360agent/migrations/__pycache__/124_add_infected_domains_vendor.cpython-311.opt-1.pyc0000644000000000000000000000210000000000000026632 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdd|dS)Ninfected_domain_listFzgoogle-safe-browsing)nulldefault)vendor)orm add_fieldspw TextFielddeleteexecute)migratordatabasefakekwargsInfectedDomainss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_infected_domains_vendor.pymigratershl#9:O |0FGGG$$&&&&&c dS)N)rrrrs rrollbackrsDr)F)loggingpeeweer getLogger__name__loggerrrrrrrs^  8 $ $''''      rdefence360agent/migrations/__pycache__/124_add_infected_domains_vendor.cpython-311.pyc0000644000000000000000000000210000000000000025673 0ustar r_jFddlZddlZejeZddZddZdS)NFc |jd}||tjdd|dS)Ninfected_domain_listFzgoogle-safe-browsing)nulldefault)vendor)orm add_fieldspw TextFielddeleteexecute)migratordatabasefakekwargsInfectedDomainss o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/124_add_infected_domains_vendor.pymigratershl#9:O |0FGGG$$&&&&&c dS)N)rrrrs rrollbackrsDr)F)loggingpeeweer getLogger__name__loggerrrrrrrs^  8 $ $''''      rdefence360agent/migrations/__pycache__/125_rescan_scan_type.cpython-311.opt-1.pyc0000644000000000000000000000537100000000000024510 0ustar r_jOddlZddlmZmZddlZddlmZddlmZejdddZ ej e Z e j e je je je jfZd d Zd d ZdS) N)datetime timedelta)importer)split_for_chunkzimav.malwarelib.configMalwareScanType)modulenamedefaultFc |jd}|jd}tjtdz }t ||j| |j |k}t|D]l}| |j |\} } || | m| |j |k\} } || | ||t!jdt!jdt(gdS) N malware_hits malware_scans)daysFz type in {})null constraints)type)ormrnowr timestamplistselectidjoinwherestartedrdeletein_sql change_fieldspw CharFieldCheckformattypes) migratordatabasefakekwargs MalwareHit MalwareScandatehits_to_deletechunkrparamss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/125_rescan_scan_type.pymigrater0sn-J,/K LNNYB/// / : : < rAs((((((((******111111(, #*;T  8 $ $$  6      r1defence360agent/migrations/__pycache__/125_rescan_scan_type.cpython-311.pyc0000644000000000000000000000537100000000000023551 0ustar r_jOddlZddlmZmZddlZddlmZddlmZejdddZ ej e Z e j e je je je jfZd d Zd d ZdS) N)datetime timedelta)importer)split_for_chunkzimav.malwarelib.configMalwareScanType)modulenamedefaultFc |jd}|jd}tjtdz }t ||j| |j |k}t|D]l}| |j |\} } || | m| |j |k\} } || | ||t!jdt!jdt(gdS) N malware_hits malware_scans)daysFz type in {})null constraints)type)ormrnowr timestamplistselectidjoinwherestartedrdeletein_sql change_fieldspw CharFieldCheckformattypes) migratordatabasefakekwargs MalwareHit MalwareScandatehits_to_deletechunkrparamss d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/125_rescan_scan_type.pymigrater0sn-J,/K LNNYB/// / : : < rAs((((((((******111111(, #*;T  8 $ $$  6      r1././@LongLink0000644000000000000000000000015000000000000007767 Lustar defence360agent/migrations/__pycache__/126_add_malware_scan_modified_files_option.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/126_add_malware_scan_modified_files_option.cpython-311.opt-1.0000644000000000000000000000322200000000000030324 0ustar r_j|ddlZddlZddlmZmZddlmZedefdefdZddZdS) N) IConfigFile LocalConfig)log_error_and_ignoreF config_filec j|rdStj|jsdSt|j5}t j|}dddn #1swxYwY|di}|dd}||d<||ddS)NMALWARE_SCANNINGscan_modified_filesF)validate) ospathexistsopenyaml safe_load setdefaultpopdict_to_config) migratordatabasefakerkwargsfconfmalware_settingsvalues z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_add_malware_scan_modified_files_option.pymigrater s  7>>+* + + k  !1~a  !!!!!!!!!!!!!!!'92>>  !6 = =E.3*+te44444sA  A$'A$c dS)N)rrrrs rrollbackr !sD)F) r r defence360agent.contracts.configrrdefence360agent.utilsrrr rr!rr$s EEEEEEEE666666 *{}} 55 5555.      r!defence360agent/migrations/__pycache__/126_add_malware_scan_modified_files_option.cpython-311.pyc0000644000000000000000000000322200000000000030101 0ustar r_j|ddlZddlZddlmZmZddlmZedefdefdZddZdS) N) IConfigFile LocalConfig)log_error_and_ignoreF config_filec j|rdStj|jsdSt|j5}t j|}dddn #1swxYwY|di}|dd}||d<||ddS)NMALWARE_SCANNINGscan_modified_filesF)validate) ospathexistsopenyaml safe_load setdefaultpopdict_to_config) migratordatabasefakerkwargsfconfmalware_settingsvalues z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_add_malware_scan_modified_files_option.pymigrater s  7>>+* + + k  !1~a  !!!!!!!!!!!!!!!'92>>  !6 = =E.3*+te44444sA  A$'A$c dS)N)rrrrs rrollbackr !sD)F) r r defence360agent.contracts.configrrdefence360agent.utilsrrr rr!rr$s EEEEEEEE666666 *{}} 55 5555.      r!defence360agent/migrations/__pycache__/126_move_malware_hits_list.cpython-311.opt-1.pyc0000644000000000000000000000426000000000000025725 0ustar r_j:ddlZddlZddlmZddlmZmZejeZ dZ ej ddZ ddZ dS) N) FILES_DIR)importerantivirus_modec tj||dS#t$rYdSt$r&}td|Yd}~dSd}~wwxYw)Nz6Failed to move HackerTrap list to the new location: %r)shutilmoveFileNotFoundError Exceptionloggererror)srcdsterrs j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_move_malware_hits_list.py_mover s  C          Dc          s A AAAFc B|rdS ddlm}tjddd}n#t$rYdSwxYwt t |_|}| }||fD]$}tt ||j %dS)Nr) HackerTrapzimav.malwarelib.subsys.malwareHackerTrapHitsSaver)modulenamedefault) defence360agent.contracts.configrrget ImportErrorstrrBASE_DIR _filepath_clean_filepathrDIR) migratordatabasefakekwargsrrsrc1src2r s rmigrater&s  ??????&l3&    $'y>>  ( ( * *D  . . 0 0DTz(( c#hh ''''((s $ 22c dS)N)r r!r"r#s rrollbackr)-sD)F)loggingrdefence360agent.filesrdefence360agent.utilsrr getLogger__name__r rskipr&r)r(r*rr1s ++++++::::::::  8 $ $   ((((.      r*defence360agent/migrations/__pycache__/126_move_malware_hits_list.cpython-311.pyc0000644000000000000000000000426000000000000024766 0ustar r_j:ddlZddlZddlmZddlmZmZejeZ dZ ej ddZ ddZ dS) N) FILES_DIR)importerantivirus_modec tj||dS#t$rYdSt$r&}td|Yd}~dSd}~wwxYw)Nz6Failed to move HackerTrap list to the new location: %r)shutilmoveFileNotFoundError Exceptionloggererror)srcdsterrs j/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/126_move_malware_hits_list.py_mover s  C          Dc          s A AAAFc B|rdS ddlm}tjddd}n#t$rYdSwxYwt t |_|}| }||fD]$}tt ||j %dS)Nr) HackerTrapzimav.malwarelib.subsys.malwareHackerTrapHitsSaver)modulenamedefault) defence360agent.contracts.configrrget ImportErrorstrrBASE_DIR _filepath_clean_filepathrDIR) migratordatabasefakekwargsrrsrc1src2r s rmigrater&s  ??????&l3&    $'y>>  ( ( * *D  . . 0 0DTz(( c#hh ''''((s $ 22c dS)N)r r!r"r#s rrollbackr)-sD)F)loggingrdefence360agent.filesrdefence360agent.utilsrr getLogger__name__r rskipr&r)r(r*rr1s ++++++::::::::  8 $ $   ((((.      r*defence360agent/migrations/__pycache__/127_remove_malware_hit_mode.cpython-311.opt-1.pyc0000644000000000000000000000124300000000000026041 0ustar r_jddZddZdS)Fc L|jd}||ddS)N malware_hitsmode)orm remove_fields)migratordatabasefakekwargs MalwareHits k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/127_remove_malware_hit_mode.pymigrater s*n-J :v.....c dS)N)rrr r s r rollbackrsDrN)F)r rrrr rs7////       rdefence360agent/migrations/__pycache__/127_remove_malware_hit_mode.cpython-311.pyc0000644000000000000000000000124300000000000025102 0ustar r_jddZddZdS)Fc L|jd}||ddS)N malware_hitsmode)orm remove_fields)migratordatabasefakekwargs MalwareHits k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/127_remove_malware_hit_mode.pymigrater s*n-J :v.....c dS)N)rrr r s r rollbackrsDrN)F)r rrrr rs7////       rdefence360agent/migrations/__pycache__/128_move_cleanup_storage_files.cpython-311.opt-1.pyc0000644000000000000000000000727600000000000026564 0ustar r_jddlZddlZddlZddlmZmZddlmZddlm Z ej dddZ ej e ZdZd Zd d Zd d ZdS)N) CharFieldModel)importer) FilenameFieldzimav.malwarelib.cleanup.storageCleanupStorage)modulenamedefaultc2Gfddt}|S)zl Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField ceZdZGfddZedZedZedZedZ e de fdZ dS) get_model..MalwareHitceZdZdZZdS)"get_model..MalwareHit.Meta malware_hitsN)__name__ __module__ __qualname__db_tabledatabasedbsn/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/128_move_cleanup_storage_files.pyMetars%HHHHrF)nullTreturnc tjj|j|j|jgS#t$rYdSwxYw)zZ Get file name for cleanup storage :return: file name N)ospathextsepjoinuserhashsize TypeError)selfs r storage_namez*get_model..MalwareHit.storage_name#sN  w~**DIty$)+LMMM   tt s58 AAN) rrrrrr"r orig_filer#r$propertystrr'rsr MalwareHitr s          ye$$$!Mu--- yd###yd###  #       rr+)r)rr+s` r get_modelr,s? U* rc,ttjj||f\}}tt||f\}} t j||dS#t$rYdSt$r&}t d|Yd}~dSd}~wwxYw)Nz2Failed to move stored file to the new location: %r) maprrjoinpathr*shutilmoveFileNotFoundError Exceptionloggererror)srcdsterrs r_mover91s>&/#s<s rrollbackrCKsDr)F)loggingrr0peeweerrdefence360agent.utilsr$defence360agent.model.simplificationrgetr getLoggerrr4r,r9r@rCrBrrrJs ########******>>>>>> ,    8 $ $<PPP          rdefence360agent/migrations/__pycache__/128_move_cleanup_storage_files.cpython-311.pyc0000644000000000000000000000727600000000000025625 0ustar r_jddlZddlZddlZddlmZmZddlmZddlm Z ej dddZ ej e ZdZd Zd d Zd d ZdS)N) CharFieldModel)importer) FilenameFieldzimav.malwarelib.cleanup.storageCleanupStorage)modulenamedefaultc2Gfddt}|S)zl Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField ceZdZGfddZedZedZedZedZ e de fdZ dS) get_model..MalwareHitceZdZdZZdS)"get_model..MalwareHit.Meta malware_hitsN)__name__ __module__ __qualname__db_tabledatabasedbsn/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/128_move_cleanup_storage_files.pyMetars%HHHHrF)nullTreturnc tjj|j|j|jgS#t$rYdSwxYw)zZ Get file name for cleanup storage :return: file name N)ospathextsepjoinuserhashsize TypeError)selfs r storage_namez*get_model..MalwareHit.storage_name#sN  w~**DIty$)+LMMM   tt s58 AAN) rrrrrr"r orig_filer#r$propertystrr'rsr MalwareHitr s          ye$$$!Mu--- yd###yd###  #       rr+)r)rr+s` r get_modelr,s? U* rc,ttjj||f\}}tt||f\}} t j||dS#t$rYdSt$r&}t d|Yd}~dSd}~wwxYw)Nz2Failed to move stored file to the new location: %r) maprrjoinpathr*shutilmoveFileNotFoundError Exceptionloggererror)srcdsterrs r_mover91s>&/#s<s rrollbackrCKsDr)F)loggingrr0peeweerrdefence360agent.utilsr$defence360agent.model.simplificationrgetr getLoggerrr4r,r9r@rCrBrrrJs ########******>>>>>> ,    8 $ $<PPP          rdefence360agent/migrations/__pycache__/129_fixed_cagefs_unmount.cpython-311.opt-1.pyc0000644000000000000000000000614600000000000025371 0ustar r_jlddlZddlZddlZddlmZddlmZmZdZdZ dZ dZ edd d gfd Z d Z eejd e ddZddZddZdS)N) lru_cache)retry_onrun_with_umaskcagefsrestartz/usr/sbin/cagefsctlz --wait-lockz/usr/binz/binc4td|DS)z6Return whether we can find systemctl in given *paths*.c3K|]A}tjtj|dVBdS) systemctlN)ospathisfilejoin).0ps h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/129_fixed_cagefs_unmount.py z$systemctl_present..s@KKrw~~bgll1k::;;KKKKKK)any)pathss rsystemctl_presentr s! KKUKKK K KKrctrdttg}ndttg} tj|n#t $rYnwxYwt jddS)Nr service)r_COMMAND _SERVICE_NAME subprocess check_call Exceptiontimesleep)excicmds r_restart_cagefsr%st3Hm4-2 c""""      JqMMMMMsA AAT) max_trieson_errorsilentcHtj|dtjdS)NF)shellstderr)r check_outputSTDOUT)r$s r_execute_commandr/s%CuZ5FGGGGGGrFc |rdSttdgttdgg}t|5tjtr|D]}t |ddddS#1swxYwYdS)Nz--force-update-etcz --remount-all)_CAGEFSCTL_TOOL _WAIT_LOCKrr r existsr/)migratordatabasefakeumaskkwargscmd_listr$s rmigrater;)s  *&:; *o6H   && 7>>/ * * & & & %%%%&&&&&&&&&&&&&&&&&&s9A88A<?A<c dS)N)r5r6r7r9s rrollbackr>6sDr)Fr0)F)rr r functoolsrdefence360agent.utilsrrrrr2r3rr%CalledProcessErrorr/r;r>r=rrrBs :::::::: '   1'0LLLL    !    HH  H & & & &      rdefence360agent/migrations/__pycache__/129_fixed_cagefs_unmount.cpython-311.pyc0000644000000000000000000000614600000000000024432 0ustar r_jlddlZddlZddlZddlmZddlmZmZdZdZ dZ dZ edd d gfd Z d Z eejd e ddZddZddZdS)N) lru_cache)retry_onrun_with_umaskcagefsrestartz/usr/sbin/cagefsctlz --wait-lockz/usr/binz/binc4td|DS)z6Return whether we can find systemctl in given *paths*.c3K|]A}tjtj|dVBdS) systemctlN)ospathisfilejoin).0ps h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/129_fixed_cagefs_unmount.py z$systemctl_present..s@KKrw~~bgll1k::;;KKKKKK)any)pathss rsystemctl_presentr s! KKUKKK K KKrctrdttg}ndttg} tj|n#t $rYnwxYwt jddS)Nr service)r_COMMAND _SERVICE_NAME subprocess check_call Exceptiontimesleep)excicmds r_restart_cagefsr%st3Hm4-2 c""""      JqMMMMMsA AAT) max_trieson_errorsilentcHtj|dtjdS)NF)shellstderr)r check_outputSTDOUT)r$s r_execute_commandr/s%CuZ5FGGGGGGrFc |rdSttdgttdgg}t|5tjtr|D]}t |ddddS#1swxYwYdS)Nz--force-update-etcz --remount-all)_CAGEFSCTL_TOOL _WAIT_LOCKrr r existsr/)migratordatabasefakeumaskkwargscmd_listr$s rmigrater;)s  *&:; *o6H   && 7>>/ * * & & & %%%%&&&&&&&&&&&&&&&&&&s9A88A<?A<c dS)N)r5r6r7r9s rrollbackr>6sDr)Fr0)F)rr r functoolsrdefence360agent.utilsrrrrr2r3rr%CalledProcessErrorr/r;r>r=rrrBs :::::::: '   1'0LLLL    !    HH  H & & & &      rdefence360agent/migrations/__pycache__/130_add_messages_to_send.cpython-311.opt-1.pyc0000644000000000000000000000264400000000000025316 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) FloatFieldModel BlobFieldcXeZdZGddZedZedZdS) MessageToSendceZdZdZdS)MessageToSend.Metamessages_to_sendN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/130_add_messages_to_send.pyMetar s%rrF)nullN)r r r rr timestamprmessagerrrrrs\&&&&&&&& &&&IiU###GGGrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s -(((((rc J|jd}||dS)Nr )orm drop_model)rrrrrs rrollbackr s)L!34M  &&&&&rN)F)peeweerrrrrr rrrr"s//////////$$$$$E$$$))))''''''rdefence360agent/migrations/__pycache__/130_add_messages_to_send.cpython-311.pyc0000644000000000000000000000264400000000000024357 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) FloatFieldModel BlobFieldcXeZdZGddZedZedZdS) MessageToSendceZdZdZdS)MessageToSend.Metamessages_to_sendN)__name__ __module__ __qualname__db_tableh/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/130_add_messages_to_send.pyMetar s%rrF)nullN)r r r rr timestamprmessagerrrrrs\&&&&&&&& &&&IiU###GGGrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s -(((((rc J|jd}||dS)Nr )orm drop_model)rrrrrs rrollbackr s)L!34M  &&&&&rN)F)peeweerrrrrr rrrr"s//////////$$$$$E$$$))))''''''rdefence360agent/migrations/__pycache__/131_incident_timestamp_index.cpython-311.opt-1.pyc0000644000000000000000000000121100000000000026221 0ustar r_j1ddZddZdS)Fc 0|ddS)NzECREATE INDEX IF NOT EXISTS incident_timestamp ON incident (timestamp))sqlmigratordatabasefakekwargss l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/131_incident_timestamp_index.pymigrater s% LLOc dS)Nrs r rollbackr sDr N)F)r rr r r rs7       r defence360agent/migrations/__pycache__/131_incident_timestamp_index.cpython-311.pyc0000644000000000000000000000121100000000000025262 0ustar r_j1ddZddZdS)Fc 0|ddS)NzECREATE INDEX IF NOT EXISTS incident_timestamp ON incident (timestamp))sqlmigratordatabasefakekwargss l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/131_incident_timestamp_index.pymigrater s% LLOc dS)Nrs r rollbackr sDr N)F)r rr r r rs7       r defence360agent/migrations/__pycache__/132_add_timestamp_field.cpython-311.opt-1.pyc0000644000000000000000000000177600000000000025151 0ustar r_jFddlZddlZejeZddZddZdS)NFc t|jd}||tjddS)N malware_hitsT)null) timestamp)orm add_fieldspw FloatFieldmigratordatabasefakekwargs MalwareHitss g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/132_add_timestamp_field.pymigrater s:,~.K  r}$/G/G/GHHHHHc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs*,~.K ; 44444r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $IIII 555555rdefence360agent/migrations/__pycache__/132_add_timestamp_field.cpython-311.pyc0000644000000000000000000000177600000000000024212 0ustar r_jFddlZddlZejeZddZddZdS)NFc t|jd}||tjddS)N malware_hitsT)null) timestamp)orm add_fieldspw FloatFieldmigratordatabasefakekwargs MalwareHitss g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/132_add_timestamp_field.pymigrater s:,~.K  r}$/G/G/GHHHHHc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs*,~.K ; 44444r)F)loggingpeeweer getLogger__name__loggerrrrrrsd  8 $ $IIII 555555rdefence360agent/migrations/__pycache__/133_add_scope_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000234300000000000026335 0ustar r_jGPddlZddlZejeZd\ZZddZddZ dS)N)localgroupFc |jd}||tjdtjdt dt dgdS)NiplistTz scope in ('z','z'))null constraints)scope)orm add_fieldspw CharFieldCheck SCOPE_LOCAL SCOPE_GROUPmigratordatabasefakekwargsip_lists m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/133_add_scope_field_to_iplist.pymigrater spl8$G l;;; LMM   c L|jd}||ddS)Nrr )r remove_fieldsrs rrollbackrs*l8$G 7G,,,,,r)F) loggingpeeweer getLogger__name__loggerrrrrrrr#sj  8 $ $+ [    ------rdefence360agent/migrations/__pycache__/133_add_scope_field_to_iplist.cpython-311.pyc0000644000000000000000000000234300000000000025376 0ustar r_jGPddlZddlZejeZd\ZZddZddZ dS)N)localgroupFc |jd}||tjdtjdt dt dgdS)NiplistTz scope in ('z','z'))null constraints)scope)orm add_fieldspw CharFieldCheck SCOPE_LOCAL SCOPE_GROUPmigratordatabasefakekwargsip_lists m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/133_add_scope_field_to_iplist.pymigrater spl8$G l;;; LMM   c L|jd}||ddS)Nrr )r remove_fieldsrs rrollbackrs*l8$G 7G,,,,,r)F) loggingpeeweer getLogger__name__loggerrrrrrrr#sj  8 $ $+ [    ------rdefence360agent/migrations/__pycache__/134_change_default_of_intensity_ram.cpython-311.opt-1.pyc0000644000000000000000000000254300000000000027550 0ustar r_jWRddlZddlmZejeZddZddZddZdS) N) ConfigFilecdddii} t|}||dS#t$rtdYdSwxYw)NMALWARE_SCAN_INTENSITYrami)pathz*Failed to set malware scan schedule config)rdict_to_config Exceptionlogger exception)rconfig config_files s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/134_change_default_of_intensity_ram.py_update_configrs 4# F G d+++ ""6***** GGGEFFFFFFGs%/$AAFc *|rdStdSN)rmigratordatabasefakekwargss rmigraters! c dSrrs rrollbackrsDrr)F) logging defence360agent.contracts.configr getLogger__name__r rrrrrrr s|777777  8 $ $ G G G G      rdefence360agent/migrations/__pycache__/134_change_default_of_intensity_ram.cpython-311.pyc0000644000000000000000000000254300000000000026611 0ustar r_jWRddlZddlmZejeZddZddZddZdS) N) ConfigFilecdddii} t|}||dS#t$rtdYdSwxYw)NMALWARE_SCAN_INTENSITYrami)pathz*Failed to set malware scan schedule config)rdict_to_config Exceptionlogger exception)rconfig config_files s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/134_change_default_of_intensity_ram.py_update_configrs 4# F G d+++ ""6***** GGGEFFFFFFGs%/$AAFc *|rdStdSN)rmigratordatabasefakekwargss rmigraters! c dSrrs rrollbackrsDrr)F) logging defence360agent.contracts.configr getLogger__name__r rrrrrrr s|777777  8 $ $ G G G G      rdefence360agent/migrations/__pycache__/135_export_proactive.cpython-311.opt-1.pyc0000644000000000000000000000504700000000000024566 0ustar r_jddlZddlZddlZejeZd\ZZdZd eZ dZ dZ d dZ d d ZdS) N)z proactive.csvzproactive_env.csvzSELECT id, timestamp, ip, ip_int, ip_version, ip_country_id, description, action, host, path, url, count, uid, gid, rule_id, rule_name FROM proactive ORDER BY timestamp DESC LIMIT ?z SELECT proactive_env.event_id, proactive_env.name, proactive_env.value FROM proactive_env INNER JOIN ({}) pa ON proactive_env.event_id=pa.id z%/var/lib/imunify360-php-daemon/exportc\ttfttffD]\}}|||f}t t j||ddd5}tj |}| |dddn #1swxYwYdS)Nwzutf-8)newlineencoding) PROACTIVE_CSV PROACTIVE_SQLPROACTIVE_ENV_CSVPROACTIVE_ENV_SQL execute_sqlopenospathjoincsvwriter writerows)database target_dir events_numfilenamequerycurcsvfile csv_writers d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_export_proactive.pyexportrs  & -. & &%""5:-88  GLLX . .      & G,,J   % % % & & & & & & & & & & & & & & & & &s**B  B$ 'B$ Fc |rdS tjtdt|tddS#t$rt dYdSwxYw)NT)exist_okiz'Failed to export proactive defence data)rmakedirs EXPORT_DIRr Exceptionlogger exceptionmigratorrfakekwargss rmigrater)%s D J....xT***** DDDBCCCCCCDs19$A! A!c dS)Nr%s rrollbackr,0sD)F)rrlogging getLogger__name__r#rr r formatr r!rr)r,r+r-rr2s   8 $ $#G  2  F 5  & & & DDDD      r-defence360agent/migrations/__pycache__/135_export_proactive.cpython-311.pyc0000644000000000000000000000504700000000000023627 0ustar r_jddlZddlZddlZejeZd\ZZdZd eZ dZ dZ d dZ d d ZdS) N)z proactive.csvzproactive_env.csvzSELECT id, timestamp, ip, ip_int, ip_version, ip_country_id, description, action, host, path, url, count, uid, gid, rule_id, rule_name FROM proactive ORDER BY timestamp DESC LIMIT ?z SELECT proactive_env.event_id, proactive_env.name, proactive_env.value FROM proactive_env INNER JOIN ({}) pa ON proactive_env.event_id=pa.id z%/var/lib/imunify360-php-daemon/exportc\ttfttffD]\}}|||f}t t j||ddd5}tj |}| |dddn #1swxYwYdS)Nwzutf-8)newlineencoding) PROACTIVE_CSV PROACTIVE_SQLPROACTIVE_ENV_CSVPROACTIVE_ENV_SQL execute_sqlopenospathjoincsvwriter writerows)database target_dir events_numfilenamequerycurcsvfile csv_writers d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_export_proactive.pyexportrs  & -. & &%""5:-88  GLLX . .      & G,,J   % % % & & & & & & & & & & & & & & & & &s**B  B$ 'B$ Fc |rdS tjtdt|tddS#t$rt dYdSwxYw)NT)exist_okiz'Failed to export proactive defence data)rmakedirs EXPORT_DIRr Exceptionlogger exceptionmigratorrfakekwargss rmigrater)%s D J....xT***** DDDBCCCCCCDs19$A! A!c dS)Nr%s rrollbackr,0sD)F)rrlogging getLogger__name__r#rr r formatr r!rr)r,r+r-rr2s   8 $ $#G  2  F 5  & & & DDDD      r-defence360agent/migrations/__pycache__/135_make_completed_nullable.cpython-311.opt-1.pyc0000644000000000000000000000142500000000000026014 0ustar r_j:ddZddZdS)Fc L|jd}||ddSN malware_scans completed)orm drop_not_nullmigratordatabasefakekwargs MalwareScans k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_make_completed_nullable.pymigraters*,/K ; 44444c L|jd}||ddSr)r add_not_nullrs rrollbackrs*,/K +{33333rN)F)rrrrrs75555 444444rdefence360agent/migrations/__pycache__/135_make_completed_nullable.cpython-311.pyc0000644000000000000000000000142500000000000025055 0ustar r_j:ddZddZdS)Fc L|jd}||ddSN malware_scans completed)orm drop_not_nullmigratordatabasefakekwargs MalwareScans k/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/135_make_completed_nullable.pymigraters*,/K ; 44444c L|jd}||ddSr)r add_not_nullrs rrollbackrs*,/K +{33333rN)F)rrrrrs75555 444444rdefence360agent/migrations/__pycache__/136_drop_proactive.cpython-311.opt-1.pyc0000644000000000000000000000131700000000000024206 0ustar r_jddZddZdS)Fc ||jd||jddS)N proactive proactive_env) remove_modelormmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/136_drop_proactive.pymigrater s> (,{3444 (,788888c dS)Nrs r rollbackrsDrN)F)r rrrr rs79999       rdefence360agent/migrations/__pycache__/136_drop_proactive.cpython-311.pyc0000644000000000000000000000131700000000000023247 0ustar r_jddZddZdS)Fc ||jd||jddS)N proactive proactive_env) remove_modelormmigratordatabasefakekwargss b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/136_drop_proactive.pymigrater s> (,{3444 (,788888c dS)Nrs r rollbackrsDrN)F)r rrrr rs79999       rdefence360agent/migrations/__pycache__/137_swap_initiator_and_cause.cpython-311.opt-1.pyc0000644000000000000000000000233500000000000026226 0ustar r_j>ddlZejeZddZddZdS)NFc |jd} |D]8}|jdvr|j|jc|_|_|9dS#t $r%}t |Yd}~dSd}~wwxYw)Nmalware_history)manualz on-demandrealtime)ormselect initiatorcausesave Exceptionlogger exception)migratordatabasefakekwargsMalwareHistoryentryes l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/137_swap_initiator_and_cause.pymigraters\"34N#**,,  E"EEE/4 , U_ JJLLLL   sA A B (BB c dS)N)rrrrs rrollbackrsD)F)logging getLogger__name__r rrrrrrsR  8 $ $      rdefence360agent/migrations/__pycache__/137_swap_initiator_and_cause.cpython-311.pyc0000644000000000000000000000233500000000000025267 0ustar r_j>ddlZejeZddZddZdS)NFc |jd} |D]8}|jdvr|j|jc|_|_|9dS#t $r%}t |Yd}~dSd}~wwxYw)Nmalware_history)manualz on-demandrealtime)ormselect initiatorcausesave Exceptionlogger exception)migratordatabasefakekwargsMalwareHistoryentryes l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/137_swap_initiator_and_cause.pymigraters\"34N#**,,  E"EEE/4 , U_ JJLLLL   sA A B (BB c dS)N)rrrrs rrollbackrsD)F)logging getLogger__name__r rrrrrrsR  8 $ $      rdefence360agent/migrations/__pycache__/138_move_rapid_scan_dir.cpython-311.opt-1.pyc0000644000000000000000000000526000000000000025160 0ustar r_jVlddlZddlZddlZddlmZddlmZejdddZd dZ d d Z dS) N) hosting_panel)importerzimav.malwarelib.utils.user_list panel_users)modulenamedefaultFc |rdStj}tj||t }|D]}t j|d}t|jdz |j z } tj |d} n#t$rYuwxYw| || kr tj|| #t$rYwxYwdSNhomez.rapid-scan-dbasyncionew_event_loopset_event_looprun_until_completerpathlibPathstrparentrr HostingPanelget_rapid_scan_db_dirOSErrorshutilmove migratordatabasefakekwargsloopusersuserpath_objold_pathnew_paths g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/138_move_rapid_scan_dir.pymigrater& s(   ! # #D 4    # #KMM 2 2E<V --x)99HMIJJ $133IIV HH    H   x833   K( + + + +    D s$ ,B88 CCC(( C54C5c tj}tj||t }|D]}t j|d}t|jdz |j z } tj |d} n#t$rYuwxYw| || kr tj| |#t$rYwxYwdSr r rs r%rollbackr(%s  ! # #D 4    # #KMM 2 2E<V --x)99HMIJJ $133IIV HH    H   x833   K( + + + +    D s$,B44 CCC$$ C10C1)F) r rrdefence360agent.subsys.panelsrdefence360agent.utilsrgetrr&r(r%r.s 777777******hl ,=$ 0r-defence360agent/migrations/__pycache__/138_move_rapid_scan_dir.cpython-311.pyc0000644000000000000000000000526000000000000024221 0ustar r_jVlddlZddlZddlZddlmZddlmZejdddZd dZ d d Z dS) N) hosting_panel)importerzimav.malwarelib.utils.user_list panel_users)modulenamedefaultFc |rdStj}tj||t }|D]}t j|d}t|jdz |j z } tj |d} n#t$rYuwxYw| || kr tj|| #t$rYwxYwdSNhomez.rapid-scan-dbasyncionew_event_loopset_event_looprun_until_completerpathlibPathstrparentrr HostingPanelget_rapid_scan_db_dirOSErrorshutilmove migratordatabasefakekwargsloopusersuserpath_objold_pathnew_paths g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/138_move_rapid_scan_dir.pymigrater& s(   ! # #D 4    # #KMM 2 2E<V --x)99HMIJJ $133IIV HH    H   x833   K( + + + +    D s$ ,B88 CCC(( C54C5c tj}tj||t }|D]}t j|d}t|jdz |j z } tj |d} n#t$rYuwxYw| || kr tj| |#t$rYwxYwdSr r rs r%rollbackr(%s  ! # #D 4    # #KMM 2 2E<V --x)99HMIJJ $133IIV HH    H   x833   K( + + + +    D s$,B44 CCC$$ C10C1)F) r rrdefence360agent.subsys.panelsrdefence360agent.utilsrgetrr&r(r%r.s 777777******hl ,=$ 0r-defence360agent/migrations/__pycache__/139_generic_modsec_config.cpython-311.opt-1.pyc0000644000000000000000000000121600000000000025462 0ustar r_jddZddZdS)Fc dS)z Rely on install-vendors to update modsec.conf on the 1st install. Drop support for updating old imunify360 versions without modsec.conf.d/ Nmigratordatabasefakekwargss i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/139_generic_modsec_config.pymigrater sc dS)Nrrs r rollbackr sDr N)F)r r rr r rs7      r defence360agent/migrations/__pycache__/139_generic_modsec_config.cpython-311.pyc0000644000000000000000000000121600000000000024523 0ustar r_jddZddZdS)Fc dS)z Rely on install-vendors to update modsec.conf on the 1st install. Drop support for updating old imunify360 versions without modsec.conf.d/ Nmigratordatabasefakekwargss i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/139_generic_modsec_config.pymigrater sc dS)Nrrs r rollbackr sDr N)F)r r rr r rs7      r defence360agent/migrations/__pycache__/140_cast_malware_hit_orig_file_as_blob.cpython-311.opt-1.pyc0000644000000000000000000000130300000000000030162 0ustar r_j#ddZddZdS)Fc 8|rdS|ddS)Nz^UPDATE malware_hits SET orig_file = CAST(orig_file AS BLOB) WHERE typeof(orig_file) != "blob";)sqlmigratordatabasefakekwargss v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.pymigrater s4  LL -c dS)Nrs r rollbackr sDr N)F)r rr r r rs7      r defence360agent/migrations/__pycache__/140_cast_malware_hit_orig_file_as_blob.cpython-311.pyc0000644000000000000000000000130300000000000027223 0ustar r_j#ddZddZdS)Fc 8|rdS|ddS)Nz^UPDATE malware_hits SET orig_file = CAST(orig_file AS BLOB) WHERE typeof(orig_file) != "blob";)sqlmigratordatabasefakekwargss v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.pymigrater s4  LL -c dS)Nrs r rollbackr sDr N)F)r rr r r rs7      r defence360agent/migrations/__pycache__/141_drop_last_user_scans.cpython-311.opt-1.pyc0000644000000000000000000000123500000000000025375 0ustar r_jddZddZdS)Fc \d|jvr"||jddSdS)Nlast_user_scans)orm remove_modelmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/141_drop_last_user_scans.pymigrater s9HL((hl+<=>>>>>)(c dS)Nrs r rollbackrsDr N)F)r rrr r rs7????       r defence360agent/migrations/__pycache__/141_drop_last_user_scans.cpython-311.pyc0000644000000000000000000000123500000000000024436 0ustar r_jddZddZdS)Fc \d|jvr"||jddSdS)Nlast_user_scans)orm remove_modelmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/141_drop_last_user_scans.pymigrater s9HL((hl+<=>>>>>)(c dS)Nrs r rollbackrsDr N)F)r rrr r rs7????       r defence360agent/migrations/__pycache__/143_malware_hit_cascade_delete.cpython-311.opt-1.pyc0000644000000000000000000000707200000000000026451 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NcJeZdZGddZejdZdS) MalwareScanceZdZdZdS)MalwareScan.Meta malware_scansN__name__ __module__ __qualname__db_tablen/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/143_malware_hit_cascade_delete.pyMetars"rrT) primary_keyN)r r r rpeewee CharFieldscanidr rrrrsM########V $ / / /FFFrrceZdZGddZejZejedddZ ej dZ ej dZ ej dZejddZej dd Zej d Zej d Zejd Zej d Zejd Zed ZdS) MalwareHitceZdZdZdS)MalwareHit.Meta malware_hitsNrr rrrr s!rrFhitsCASCADE)null related_name on_delete)r)rdefaultzai-bolitTfound)rc8td|jjS)Nc|jSN) column_name)fields rz,MalwareHit.get_field_names.. s !2r)map_meta sorted_fields)clss rget_field_nameszMalwareHit.get_field_namess22CI4KLLLrN)r r r rrPrimaryKeyFieldidForeignKeyFieldrrruser BlobField orig_filetype BooleanField maliciousvendorhashsize FloatField timestampstatus cleaned_at classmethodr+r rrrr sf""""""""   ! !B #V #%f F 6  ' ' 'D  e,,,I 6  ' ' 'D##>>>I V 5* = = =F 6  & & &D 6  & & &D!!t,,,I V g . . .F""---JMM[MMMrrFc 6|d|tdt}|d||ddS)Nz4ALTER TABLE malware_hits RENAME TO malware_hits_old;,z@INSERT INTO malware_hits ({0}) SELECT {0} FROM malware_hits_old;zDROP TABLE malware_hits_old;)sql create_modelrjoinr+format)migratordatabasefakekwargsmalware_hit_fieldss rmigraterH#s LLGHHH *%%%*"<"<">">?? LLJ " # # LL/00000rc dSr#r )rCrDrErFs rrollbackrJ/sDr)F)rModelrrrHrJr rrrLs 00000&,000MMMMMMMM0 1 1 1 1      rdefence360agent/migrations/__pycache__/143_malware_hit_cascade_delete.cpython-311.pyc0000644000000000000000000000707200000000000025512 0ustar r_jjddlZGddejZGddejZd dZd dZdS) NcJeZdZGddZejdZdS) MalwareScanceZdZdZdS)MalwareScan.Meta malware_scansN__name__ __module__ __qualname__db_tablen/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/143_malware_hit_cascade_delete.pyMetars"rrT) primary_keyN)r r r rpeewee CharFieldscanidr rrrrsM########V $ / / /FFFrrceZdZGddZejZejedddZ ej dZ ej dZ ej dZejddZej dd Zej d Zej d Zejd Zej d Zejd Zed ZdS) MalwareHitceZdZdZdS)MalwareHit.Meta malware_hitsNrr rrrr s!rrFhitsCASCADE)null related_name on_delete)r)rdefaultzai-bolitTfound)rc8td|jjS)Nc|jSN) column_name)fields rz,MalwareHit.get_field_names.. s !2r)map_meta sorted_fields)clss rget_field_nameszMalwareHit.get_field_namess22CI4KLLLrN)r r r rrPrimaryKeyFieldidForeignKeyFieldrrruser BlobField orig_filetype BooleanField maliciousvendorhashsize FloatField timestampstatus cleaned_at classmethodr+r rrrr sf""""""""   ! !B #V #%f F 6  ' ' 'D  e,,,I 6  ' ' 'D##>>>I V 5* = = =F 6  & & &D 6  & & &D!!t,,,I V g . . .F""---JMM[MMMrrFc 6|d|tdt}|d||ddS)Nz4ALTER TABLE malware_hits RENAME TO malware_hits_old;,z@INSERT INTO malware_hits ({0}) SELECT {0} FROM malware_hits_old;zDROP TABLE malware_hits_old;)sql create_modelrjoinr+format)migratordatabasefakekwargsmalware_hit_fieldss rmigraterH#s LLGHHH *%%%*"<"<">">?? LLJ " # # LL/00000rc dSr#r )rCrDrErFs rrollbackrJ/sDr)F)rModelrrrHrJr rrrLs 00000&,000MMMMMMMM0 1 1 1 1      rdefence360agent/migrations/__pycache__/144_remove_clamav_config_options.cpython-311.opt-1.pyc0000644000000000000000000000317400000000000027110 0ustar r_j5hddlZddlmZmZejeZdefdefdZddZdS)N)IConfig ConfigFileF config_filec v|rdS |d}d|vrdS|ddd|ddd|ddd||dddS#t$rtd YdSwxYw) NF) normalizeMALWARE_SCANNING i360_clamdshow_clamav_results clamav_binaryT) overwritevalidatez&Failed to remove clamav config options)config_to_dictpopdict_to_config Exceptionlogger exception)migratordatabasefakerkwargsconfigs p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_clamav_config_options.pymigraters  C++e+<< V + + F!"&&|T:::!"&&'r#s@@@@@@@@  8 $ $ %:<< CC CCCC2      rdefence360agent/migrations/__pycache__/144_remove_clamav_config_options.cpython-311.pyc0000644000000000000000000000317400000000000026151 0ustar r_j5hddlZddlmZmZejeZdefdefdZddZdS)N)IConfig ConfigFileF config_filec v|rdS |d}d|vrdS|ddd|ddd|ddd||dddS#t$rtd YdSwxYw) NF) normalizeMALWARE_SCANNING i360_clamdshow_clamav_results clamav_binaryT) overwritevalidatez&Failed to remove clamav config options)config_to_dictpopdict_to_config Exceptionlogger exception)migratordatabasefakerkwargsconfigs p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_clamav_config_options.pymigraters  C++e+<< V + + F!"&&|T:::!"&&'r#s@@@@@@@@  8 $ $ %:<< CC CCCC2      rdefence360agent/migrations/__pycache__/144_remove_hash_table.cpython-311.opt-1.pyc0000644000000000000000000000112500000000000024631 0ustar r_jddZddZdS)Fc 0|ddS)Nz"DROP TABLE IF EXISTS malware_hash;)sqlmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_hash_table.pymigrater s LL566666c dS)Nrs r rollbackrsDr N)F)r rr r r rs77777      r defence360agent/migrations/__pycache__/144_remove_hash_table.cpython-311.pyc0000644000000000000000000000112500000000000023672 0ustar r_jddZddZdS)Fc 0|ddS)Nz"DROP TABLE IF EXISTS malware_hash;)sqlmigratordatabasefakekwargss e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/144_remove_hash_table.pymigrater s LL566666c dS)Nrs r rollbackrsDr N)F)r rr r r rs77777      r defence360agent/migrations/__pycache__/145_move_quarantine.cpython-311.opt-1.pyc0000644000000000000000000000076100000000000024365 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/145_move_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r defence360agent/migrations/__pycache__/145_move_quarantine.cpython-311.pyc0000644000000000000000000000076100000000000023426 0ustar r_jodZdZdZdS)z# Quarantine is removed in DEF-15234cdSN___s c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/145_move_quarantine.pymigrater DcdSrrrs rrollbackr r r N)__doc__r r rr rrs3))        r ././@LongLink0000644000000000000000000000014600000000000007774 Lustar defence360agent/migrations/__pycache__/146_malware_user_infected_cascade_delete.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/146_malware_user_infected_cascade_delete.cpython-311.opt-1.py0000644000000000000000000000077700000000000030351 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/146_malware_user_infected_cascade_delete.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/146_malware_user_infected_cascade_delete.cpython-311.pyc0000644000000000000000000000077700000000000027555 0ustar r_jddZddZdS)Fc dSNmigratordatabasefakekwargss x/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/146_malware_user_infected_cascade_delete.pymigrater Dc dSrrrs r rollbackrr r N)F)r rrr r rs7          r defence360agent/migrations/__pycache__/147_remove_vendor_field.cpython-311.opt-1.pyc0000644000000000000000000000164100000000000025205 0ustar r_jddlZddZddZdS)NFc L|jd}||ddS)N malware_hitsvendor)orm remove_fieldsmigratordatabasefakekwargsrs g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_remove_vendor_field.pymigraters*</L <22222c v|jd}||tjdddS)NrFzai-bolit)nulldefault)r)r add_fieldspeewee CharFieldrs r rollbackr sJ</L V-5*MMMr)F)rrrrr rs@ 3333 rdefence360agent/migrations/__pycache__/147_remove_vendor_field.cpython-311.pyc0000644000000000000000000000164100000000000024246 0ustar r_jddlZddZddZdS)NFc L|jd}||ddS)N malware_hitsvendor)orm remove_fieldsmigratordatabasefakekwargsrs g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_remove_vendor_field.pymigraters*</L <22222c v|jd}||tjdddS)NrFzai-bolit)nulldefault)r)r add_fieldspeewee CharFieldrs r rollbackr sJ</L V-5*MMMr)F)rrrrr rs@ 3333 rdefence360agent/migrations/__pycache__/147_user_scan_type.cpython-311.opt-1.pyc0000644000000000000000000000254400000000000024216 0ustar r_jddlZddlmZejdddZejejejej ej ej fZ d dZ d dZdS) N)importerzimav.malwarelib.configMalwareScanType)modulenamedefaultFc |jd}||tjdtjdt gdS)N malware_scansFz type in {})null constraints)type)orm change_fieldspw CharFieldCheckformattypes)migratordatabasefakekwargs MalwareScans b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_user_scan_type.pymigratersl,/K  \RXl.A.A%.H.H%I%I$J   c dS)N)rrrrs rrollbackrsDr)F)peeweerdefence360agent.utilsrgetr ON_DEMANDREALTIMEMALWARE_RESPONSE BACKGROUNDRESCANUSERrrrrrrr(s******(, #*;T $        rdefence360agent/migrations/__pycache__/147_user_scan_type.cpython-311.pyc0000644000000000000000000000254400000000000023257 0ustar r_jddlZddlmZejdddZejejejej ej ej fZ d dZ d dZdS) N)importerzimav.malwarelib.configMalwareScanType)modulenamedefaultFc |jd}||tjdtjdt gdS)N malware_scansFz type in {})null constraints)type)orm change_fieldspw CharFieldCheckformattypes)migratordatabasefakekwargs MalwareScans b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/147_user_scan_type.pymigratersl,/K  \RXl.A.A%.H.H%I%I$J   c dS)N)rrrrs rrollbackrsDr)F)peeweerdefence360agent.utilsrgetr ON_DEMANDREALTIMEMALWARE_RESPONSE BACKGROUNDRESCANUSERrrrrrrr(s******(, #*;T $        rdefence360agent/migrations/__pycache__/148_reconstruct_pickled_scan_queue.cpython-311.opt-1.pyc0000644000000000000000000000121000000000000027437 0ustar r_jdZdZdS)cdS)z Backward compatibility for reconstruction of pickled scan queue is done in the imav.malwarelib.scan.queue.py module. Migration is no longer needed. N___s r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_reconstruct_pickled_scan_queue.pymigratercdS)zDowngrade is not supportedNrrs rrollbackr r r N)rr rr rr s-%%%%%r defence360agent/migrations/__pycache__/148_reconstruct_pickled_scan_queue.cpython-311.pyc0000644000000000000000000000121000000000000026500 0ustar r_jdZdZdS)cdS)z Backward compatibility for reconstruction of pickled scan queue is done in the imav.malwarelib.scan.queue.py module. Migration is no longer needed. N___s r/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_reconstruct_pickled_scan_queue.pymigratercdS)zDowngrade is not supportedNrrs rrollbackr r r N)rr rr rr s-%%%%%r defence360agent/migrations/__pycache__/148_remove_malware_user_infected.cpython-311.opt-1.pyc0000644000000000000000000000115000000000000027070 0ustar r_jddZddZdS)Fc 0|ddS)Nz*DROP TABLE IF EXISTS malware_user_infected)sqlmigratordatabasefakekwargss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_remove_malware_user_infected.pymigrater s LL=>>>>>c dS)Nrs r rollbackrsDr N)F)r rr r r rs7????      r defence360agent/migrations/__pycache__/148_remove_malware_user_infected.cpython-311.pyc0000644000000000000000000000115000000000000026131 0ustar r_jddZddZdS)Fc 0|ddS)Nz*DROP TABLE IF EXISTS malware_user_infected)sqlmigratordatabasefakekwargss p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/148_remove_malware_user_infected.pymigrater s LL=>>>>>c dS)Nrs r rollbackrsDr N)F)r rr r r rs7????      r defence360agent/migrations/__pycache__/149_add_captcha_passed_field_to_iplist.cpython-311.opt-1.pyc0000644000000000000000000000166500000000000030203 0ustar r_jmddlZddZddZdS)NFc v|jd}||tjdddS)NiplistF)nulldefault)captcha_passed)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_add_captcha_passed_field_to_iplist.pymigratersI \( #F rE5IIIc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+ \( #F 6#344444r)F)peeweer rrrrrsC555555rdefence360agent/migrations/__pycache__/149_add_captcha_passed_field_to_iplist.cpython-311.pyc0000644000000000000000000000166500000000000027244 0ustar r_jmddlZddZddZdS)NFc v|jd}||tjdddS)NiplistF)nulldefault)captcha_passed)orm add_fieldspw BooleanFieldmigratordatabasefakekwargsIPLists v/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/149_add_captcha_passed_field_to_iplist.pymigratersI \( #F rE5IIIc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackr s+ \( #F 6#344444r)F)peeweer rrrrrsC555555rdefence360agent/migrations/__pycache__/149_make_config_inactive.cpython-311.opt-1.pyc0000644000000000000000000000300300000000000025310 0ustar r_jJdZddlZddlZejeZddZddZdS)z This migration is needed to cleanup modsec config on cPanel by removing includes for modsec2.imunify.conf File is automatically included from /etc/apache2/conf.d, thus no explicit includes are needed NFc |rdSdD]e} tjddd|gd0#t$rYrsn   8 $ $AAAA(      rdefence360agent/migrations/__pycache__/149_make_config_inactive.cpython-311.pyc0000644000000000000000000000300300000000000024351 0ustar r_jJdZddlZddlZejeZddZddZdS)z This migration is needed to cleanup modsec config on cPanel by removing includes for modsec2.imunify.conf File is automatically included from /etc/apache2/conf.d, thus no explicit includes are needed NFc |rdSdD]e} tjddd|gd0#t$rYrsn   8 $ $AAAA(      r././@LongLink0000644000000000000000000000016000000000000007770 Lustar defence360agent/migrations/__pycache__/150_update_captcha_passed_field_for_iplist_entries.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/150_update_captcha_passed_field_for_iplist_entries.cpython-310000644000000000000000000000271600000000000031006 0ustar r_j>ddlZejeZddZddZdS)NFc n|jd}|jdk|jz|jz|jdz} ||jdi|}| dS#t$rt dYdSwxYw)NiplistWHITEzdue to successful captcha passTz%Failed update to captcha_passed field) ormlistname full_accessmanualcommentcontainsupdatecaptcha_passedwhereexecute Exceptionlogger exception)migratordatabasefakekwargsIPListcaptcha_pass_conditionqs /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.pymigraters \( #F G #    M>  > " "#C D D F B MM60$7 8 8 > > "   BBB@AAAAAABsAB $B43B4c dS)N)rrrrs rrollbackrsD)F)logging getLogger__name__rrrrrrr#sV  8 $ $BBBB"      r././@LongLink0000644000000000000000000000015200000000000007771 Lustar defence360agent/migrations/__pycache__/150_update_captcha_passed_field_for_iplist_entries.cpython-311.pycdefence360agent/migrations/__pycache__/150_update_captcha_passed_field_for_iplist_entries.cpython-310000644000000000000000000000271600000000000031006 0ustar r_j>ddlZejeZddZddZdS)NFc n|jd}|jdk|jz|jz|jdz} ||jdi|}| dS#t$rt dYdSwxYw)NiplistWHITEzdue to successful captcha passTz%Failed update to captcha_passed field) ormlistname full_accessmanualcommentcontainsupdatecaptcha_passedwhereexecute Exceptionlogger exception)migratordatabasefakekwargsIPListcaptcha_pass_conditionqs /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.pymigraters \( #F G #    M>  > " "#C D D F B MM60$7 8 8 > > "   BBB@AAAAAABsAB $B43B4c dS)N)rrrrs rrollbackrsD)F)logging getLogger__name__rrrrrrr#sV  8 $ $BBBB"      rdefence360agent/migrations/__pycache__/151_change_constraint_for_iplist.cpython-311.opt-1.pyc0000644000000000000000000000211400000000000027102 0ustar r_jddlZddZddZdS)NFc |jd}d}||tjdtjdd|gdS)Niplist)WHITEBLACKGRAYGRAY_SPLASHSCREENFzlistname in ('{}')z',')null constraints)listname)orm change_fieldspw CharFieldCheckformatjoin)migratordatabasefakekwargs orm_IPListIP_LISTSs p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/151_change_constraint_for_iplist.pymigratersh'J>H -44UZZ5I5IJJKK   c dS)N)rrrrs rrollbackrsDr)F)peeweerrrrrrr sC          rdefence360agent/migrations/__pycache__/151_change_constraint_for_iplist.cpython-311.pyc0000644000000000000000000000211400000000000026143 0ustar r_jddlZddZddZdS)NFc |jd}d}||tjdtjdd|gdS)Niplist)WHITEBLACKGRAYGRAY_SPLASHSCREENFzlistname in ('{}')z',')null constraints)listname)orm change_fieldspw CharFieldCheckformatjoin)migratordatabasefakekwargs orm_IPListIP_LISTSs p/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/151_change_constraint_for_iplist.pymigratersh'J>H -44UZZ5I5IJJKK   c dS)N)rrrrs rrollbackrsDr)F)peeweerrrrrrr sC          rdefence360agent/migrations/__pycache__/152_add_listname_to_primary_key.cpython-311.opt-1.pyc0000644000000000000000000001045200000000000026725 0ustar r_j BddlmZmZmZmZmZmZmZddlZddZ ddZ dS)) BooleanField CharFieldCheck CompositeKeyForeignKeyField IntegerFieldModelNFc |jd}|jdGfddt}||d|jjDdgz}|dd| |d |d ||d ||d ||ddS)Niplistcountryc beZdZdZdZdxZ\ZZZZ d\Z Z e dZ e deddeg Zed d Ze d Zed d Zed Ze d Zed ZeddZedd Zed Zed dZedZedZ edZ!e d ede de dg Z"GddZ#dS)migrate..TMP_IPListz'iplist' db table. action_type)WHITEBLACKGRAYGRAY_SPLASHSCREEN)localgroupF)nullzlistname in ('{}')z',')r constraintsrT)defaultrcBttjSN)inttimeo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/152_add_listname_to_primary_key.pyz$migrate..TMP_IPList..ss49;;'7'7r)rrz scope in ('z')c.eZdZdZeddddZdS) migrate..TMP_IPList.Meta tmpiplistnetwork_addressnetmaskversionlistnameN)__name__ __module__ __qualname__db_tabler primary_keyrrrMetar"Ks-"H&,!9iKKKrr-N)$r(r)r*__doc__ ACTION_TYPEIP_LISTSrrrr SCOPE_LOCAL SCOPE_GROUPriprformatjoinr'r expiration imported_fromctimedeepcommentrr rcaptcha_passedmanual full_accessauto_whitelistedr$r%r&scoper-)Countrysr TMP_IPListrs  $ >  :E5$(9 $4 [ YE " " "9*11%**X2F2FGGHH   "\D   " t,,,  77   |&&&)&&&!/'555&5%@@@5$777#l--- 'zmigrate..Zs-  9     r country_idz>>>>>>U>>>@ *%%%$7  F  LLFMM88F## N    LL$%%% LL9::: z:... z<000 z4(((((rc dSrr)rOrPrQrRs rrollbackrVksDr)F) peeweerrrrrrr rrTrVrrrrXs [)[)[)[)|      rdefence360agent/migrations/__pycache__/152_add_listname_to_primary_key.cpython-311.pyc0000644000000000000000000001045200000000000025766 0ustar r_j BddlmZmZmZmZmZmZmZddlZddZ ddZ dS)) BooleanField CharFieldCheck CompositeKeyForeignKeyField IntegerFieldModelNFc |jd}|jdGfddt}||d|jjDdgz}|dd| |d |d ||d ||d ||ddS)Niplistcountryc beZdZdZdZdxZ\ZZZZ d\Z Z e dZ e deddeg Zed d Ze d Zed d Zed Ze d Zed ZeddZedd Zed Zed dZedZedZ edZ!e d ede de dg Z"GddZ#dS)migrate..TMP_IPListz'iplist' db table. action_type)WHITEBLACKGRAYGRAY_SPLASHSCREEN)localgroupF)nullzlistname in ('{}')z',')r constraintsrT)defaultrcBttjSN)inttimeo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/152_add_listname_to_primary_key.pyz$migrate..TMP_IPList..ss49;;'7'7r)rrz scope in ('z')c.eZdZdZeddddZdS) migrate..TMP_IPList.Meta tmpiplistnetwork_addressnetmaskversionlistnameN)__name__ __module__ __qualname__db_tabler primary_keyrrrMetar"Ks-"H&,!9iKKKrr-N)$r(r)r*__doc__ ACTION_TYPEIP_LISTSrrrr SCOPE_LOCAL SCOPE_GROUPriprformatjoinr'r expiration imported_fromctimedeepcommentrr rcaptcha_passedmanual full_accessauto_whitelistedr$r%r&scoper-)Countrysr TMP_IPListrs  $ >  :E5$(9 $4 [ YE " " "9*11%**X2F2FGGHH   "\D   " t,,,  77   |&&&)&&&!/'555&5%@@@5$777#l--- 'zmigrate..Zs-  9     r country_idz>>>>>>U>>>@ *%%%$7  F  LLFMM88F## N    LL$%%% LL9::: z:... z<000 z4(((((rc dSrr)rOrPrQrRs rrollbackrVksDr)F) peeweerrrrrrr rrTrVrrrrXs [)[)[)[)|      rdefence360agent/migrations/__pycache__/153_migrate_config_default_action.cpython-311.opt-1.pyc0000644000000000000000000000461700000000000027211 0ustar r_jFddlZddlZddlmZmZddlmZejeZ dedfdefdZ edefdZ d d Z dS) N) ConfigFileIConfig)log_error_and_ignorez/etc/imunify360/user_configF config_filec |rdSt|tj|sdStj|D]}tt | dS)N)username)migrate_configospathexistslistdirr)migratordatabaseuser_config_dirrfakekwargsrs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_migrate_config_default_action.pymigrater s} ; 7>>/ * *J//66z8444555566cb|d}|sdS|di}|d}|dkr>d|d<|di}|d}| |d krd |d<n |d vrd|d<ndS||d dd dS) NF) normalizeMALWARE_SCANNINGdefault_action quarantinecleanupMALWARE_CLEANUPkeep_original_files_days)cleanup_or_quarantinedeleteT) overwritevalidater)config_to_dict setdefaultgetdict_to_config)rconfigmalware_settingsrcleanup_settingskeep_original_filess rr r s  ' '% ' 8 8F (();R@@%))*:;;N%%-6)*!,,->CC.223MNN  */BS/H/H;> 7 8 > > >-6)**$%rc dS)N)rrrrs rrollbackr-8sDr)F) loggingr defence360agent.contracts.configrrdefence360agent.utilsr getLogger__name__loggerrr r-r,rrr4s @@@@@@@@666666  8 $ $ 2%:<<  66 6666,.      rdefence360agent/migrations/__pycache__/153_migrate_config_default_action.cpython-311.pyc0000644000000000000000000000461700000000000026252 0ustar r_jFddlZddlZddlmZmZddlmZejeZ dedfdefdZ edefdZ d d Z dS) N) ConfigFileIConfig)log_error_and_ignorez/etc/imunify360/user_configF config_filec |rdSt|tj|sdStj|D]}tt | dS)N)username)migrate_configospathexistslistdirr)migratordatabaseuser_config_dirrfakekwargsrs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_migrate_config_default_action.pymigrater s} ; 7>>/ * *J//66z8444555566cb|d}|sdS|di}|d}|dkr>d|d<|di}|d}| |d krd |d<n |d vrd|d<ndS||d dd dS) NF) normalizeMALWARE_SCANNINGdefault_action quarantinecleanupMALWARE_CLEANUPkeep_original_files_days)cleanup_or_quarantinedeleteT) overwritevalidater)config_to_dict setdefaultgetdict_to_config)rconfigmalware_settingsrcleanup_settingskeep_original_filess rr r s  ' '% ' 8 8F (();R@@%))*:;;N%%-6)*!,,->CC.223MNN  */BS/H/H;> 7 8 > > >-6)**$%rc dS)N)rrrrs rrollbackr-8sDr)F) loggingr defence360agent.contracts.configrrdefence360agent.utilsr getLogger__name__loggerrr r-r,rrr4s @@@@@@@@666666  8 $ $ 2%:<<  66 6666,.      rdefence360agent/migrations/__pycache__/153_update_incident_name.cpython-311.opt-1.pyc0000644000000000000000000000122600000000000025323 0ustar r_jddZddZdS)Fc 0|ddS)NzTUPDATE incident SET name='Login Blocked by cpHulk' where plugin='cphulk' and name='')sqlmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_update_incident_name.pymigrater s' LL -c dS)Nrs r rollbackrsDr N)F)r rr r r rs7      r defence360agent/migrations/__pycache__/153_update_incident_name.cpython-311.pyc0000644000000000000000000000122600000000000024364 0ustar r_jddZddZdS)Fc 0|ddS)NzTUPDATE incident SET name='Login Blocked by cpHulk' where plugin='cphulk' and name='')sqlmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/153_update_incident_name.pymigrater s' LL -c dS)Nrs r rollbackrsDr N)F)r rr r r rs7      r ././@LongLink0000644000000000000000000000015600000000000007775 Lustar defence360agent/migrations/__pycache__/154_migrate_config_user_override_malware_actions.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/154_migrate_config_user_override_malware_actions.cpython-311.0000644000000000000000000000260500000000000030656 0ustar r_jDddlmZmZmZedddefdZdZdS))IConfig LocalConfig NonBaseMergerF) config_filefakerc|rdSttjd}|di}|d}|!d|d<|d|idddSdS)N)namesT) force_read PERMISSIONSuser_override_malware_actionsF)validatewithout_defaults)rget_layer_namesconfigs_to_dict setdefaultgetdict_to_config)rr___configpermission_settingsr s /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/154_migrate_config_user_override_malware_actions.pymigraters  ,..oo&& !++M2>>$7$;$;'%%!%,?C;<"" / 0! #     -,cdS)N)rrs rrollbackrsDrN) defence360agent.contracts.configrrrrrrrrrs(3{}}5   W    ,     r././@LongLink0000644000000000000000000000015000000000000007767 Lustar defence360agent/migrations/__pycache__/154_migrate_config_user_override_malware_actions.cpython-311.pycdefence360agent/migrations/__pycache__/154_migrate_config_user_override_malware_actions.cpython-311.0000644000000000000000000000260500000000000030656 0ustar r_jDddlmZmZmZedddefdZdZdS))IConfig LocalConfig NonBaseMergerF) config_filefakerc|rdSttjd}|di}|d}|!d|d<|d|idddSdS)N)namesT) force_read PERMISSIONSuser_override_malware_actionsF)validatewithout_defaults)rget_layer_namesconfigs_to_dict setdefaultgetdict_to_config)rr___configpermission_settingsr s /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/154_migrate_config_user_override_malware_actions.pymigraters  ,..oo&& !++M2>>$7$;$;'%%!%,?C;<"" / 0! #     -,cdS)N)rrs rrollbackrsDrN) defence360agent.contracts.configrrrrrrrrrs(3{}}5   W    ,     r././@LongLink0000644000000000000000000000016000000000000007770 Lustar defence360agent/migrations/__pycache__/155_migrate_config_user_override_proactive_defense.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/155_migrate_config_user_override_proactive_defense.cpython-310000644000000000000000000000264300000000000031037 0ustar r_j!DddlmZmZmZedddefdZdZdS))IConfig LocalConfig NonBaseMergerF) config_filefakerc|rdSttjd}|di}|d}|!d|d<|d|idddSdS)N)namesT) force_read PERMISSIONSuser_override_proactive_defenseF)validatewithout_defaults)rget_layer_namesconfigs_to_dict setdefaultgetdict_to_config)rr___configpermission_settingsuser_override_malware_actionss /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/155_migrate_config_user_override_proactive_defense.pymigraters  ,..oo&& !++M2>>$7$;$;)%%!%,AE=>"" / 0! #     -,cdS)N)rrs rrollbackrsDrN) defence360agent.contracts.configrrrrrrrrr s(3{}}5   W    ,     r././@LongLink0000644000000000000000000000015200000000000007771 Lustar defence360agent/migrations/__pycache__/155_migrate_config_user_override_proactive_defense.cpython-311.pycdefence360agent/migrations/__pycache__/155_migrate_config_user_override_proactive_defense.cpython-310000644000000000000000000000264300000000000031037 0ustar r_j!DddlmZmZmZedddefdZdZdS))IConfig LocalConfig NonBaseMergerF) config_filefakerc|rdSttjd}|di}|d}|!d|d<|d|idddSdS)N)namesT) force_read PERMISSIONSuser_override_proactive_defenseF)validatewithout_defaults)rget_layer_namesconfigs_to_dict setdefaultgetdict_to_config)rr___configpermission_settingsuser_override_malware_actionss /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/155_migrate_config_user_override_proactive_defense.pymigraters  ,..oo&& !++M2>>$7$;$;)%%!%,AE=>"" / 0! #     -,cdS)N)rrs rrollbackrsDrN) defence360agent.contracts.configrrrrrrrrr s(3{}}5   W    ,     rdefence360agent/migrations/__pycache__/156_remove_default_values_from_config.cpython-311.opt-1.pyc0000644000000000000000000000174500000000000030125 0ustar r_jYdZddZddZdS)a Remove values from imunify360.config that are the same as in imunify360-base.config. Use stub migration, since for new installations imunify360-base.config is absent, so this migration is no longer needed in this case. Otherwise, when the migration has already been applied, no need to reapply. Keep the migration itself, since it was already released. To remove schema defaults from imunify360.config 159_remove_defaults_from_local_config migration is used. Fc dSNmigratordatabasefakekwargss u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/156_remove_default_values_from_config.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rsA            r defence360agent/migrations/__pycache__/156_remove_default_values_from_config.cpython-311.pyc0000644000000000000000000000174500000000000027166 0ustar r_jYdZddZddZdS)a Remove values from imunify360.config that are the same as in imunify360-base.config. Use stub migration, since for new installations imunify360-base.config is absent, so this migration is no longer needed in this case. Otherwise, when the migration has already been applied, no need to reapply. Keep the migration itself, since it was already released. To remove schema defaults from imunify360.config 159_remove_defaults_from_local_config migration is used. Fc dSNmigratordatabasefakekwargss u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/156_remove_default_values_from_config.pymigrater Dc dSrrrs r rollbackrr r N)F)__doc__r rrr r rsA            r defence360agent/migrations/__pycache__/157_move_i360_modsec_disable_conf.cpython-311.opt-1.pyc0000644000000000000000000000347400000000000026730 0ustar r_jjddlZddlZddlZddlmZejeZdZdZ dZ dZ d dZ d d Z dS) N) OsReleaseInfoz7/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.confz>/etc/apache2/conf/plesk.conf.d/vhosts/i360_modsec_disable.confz5/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.confz > K } 5 5 5 5 5 > > >   -} = = = = = = >>>s"A99%B"!B"c dS)N)rrrrs rrollbackr)sD)F)loggingr rdefence360agent.utilsr getLogger__name__rrrr r rrrrrr#s //////  8 $ $>$E$<C >>>>"      rdefence360agent/migrations/__pycache__/157_move_i360_modsec_disable_conf.cpython-311.pyc0000644000000000000000000000347400000000000025771 0ustar r_jjddlZddlZddlZddlmZejeZdZdZ dZ dZ d dZ d d Z dS) N) OsReleaseInfoz7/etc/apache2/conf/plesk.conf.d/i360_modsec_disable.confz>/etc/apache2/conf/plesk.conf.d/vhosts/i360_modsec_disable.confz5/etc/httpd/conf/plesk.conf.d/i360_modsec_disable.confz > K } 5 5 5 5 5 > > >   -} = = = = = = >>>s"A99%B"!B"c dS)N)rrrrs rrollbackr)sD)F)loggingr rdefence360agent.utilsr getLogger__name__rrrr r rrrrrr#s //////  8 $ $>$E$<C >>>>"      r././@LongLink0000644000000000000000000000014700000000000007775 Lustar defence360agent/migrations/__pycache__/158_move_i360_modsec_disable_conf_symlink.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/158_move_i360_modsec_disable_conf_symlink.cpython-311.opt-1.p0000644000000000000000000000473600000000000030145 0ustar r_jrddlZddlZddlZddlmZejeZdZdZ dZ dZ dZ dZ d d Zd d ZdS) N) OsReleaseInfoz2/etc/apache2/plesk.conf.d/i360_modsec_disable.confz 66> > K } 5 5 5 5 > > >   -} = = = = = > w~~gB B Ig    J|W - - - - - B B B   7 A A A A A A B BBs$6B %B43B4)D%D+*D+c dS)N)rrrrs rrollbackr#:sD)F)loggingr rdefence360agent.utilsr getLogger__name__rr r r r rrr r#r"r$rr)s //////  8 $ $9$C<; 9$>$ BBBB6      r$defence360agent/migrations/__pycache__/158_move_i360_modsec_disable_conf_symlink.cpython-311.pyc0000644000000000000000000000473600000000000027542 0ustar r_jrddlZddlZddlZddlmZejeZdZdZ dZ dZ dZ dZ d d Zd d ZdS) N) OsReleaseInfoz2/etc/apache2/plesk.conf.d/i360_modsec_disable.confz 66> > K } 5 5 5 5 > > >   -} = = = = = > w~~gB B Ig    J|W - - - - - B B B   7 A A A A A A B BBs$6B %B43B4)D%D+*D+c dS)N)rrrrs rrollbackr#:sD)F)loggingr rdefence360agent.utilsr getLogger__name__rr r r r rrr r#r"r$rr)s //////  8 $ $9$C<; 9$>$ BBBB6      r$defence360agent/migrations/__pycache__/159_remove_defaults_from_local_config.cpython-311.opt-1.pyc0000644000000000000000000000333600000000000030104 0ustar r_jZdZddlZddlmZddlmZejeZddZ ddZ dS) zv Remove all default values from main config (/etc/sysconfig/imunify360/imunify360.config). See DEF-17214 for details. N) LocalConfig)exclude_equalsFc <|rdS t}|d}|id}t||}||ddS#t $r&}t d|Yd}~dSd}~wwxYw)NT) force_readF)without_defaults) main_conf base_conf) overwritez(Can't overwrite local config, reason: %s)rconfig_to_dict normalizerdict_to_config Exceptionloggererror) migratordatabasefakekwargs local_config local_confdefaultsnon_default_confexcs u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/159_remove_defaults_from_local_config.pymigraters  F"}} !00D0AA ))"u)EE) H    ##$4#EEEEE FFF ?EEEEEEEEEFsA#A++ B5BBc dS)N)rrrrs rrollbackr sD)F) __doc__logging defence360agent.contracts.configr)defence360agent.contracts.config_providerr getLogger__name__rrrrrrr&s 888888DDDDDD  8 $ $FFFF$      rdefence360agent/migrations/__pycache__/159_remove_defaults_from_local_config.cpython-311.pyc0000644000000000000000000000333600000000000027145 0ustar r_jZdZddlZddlmZddlmZejeZddZ ddZ dS) zv Remove all default values from main config (/etc/sysconfig/imunify360/imunify360.config). See DEF-17214 for details. N) LocalConfig)exclude_equalsFc <|rdS t}|d}|id}t||}||ddS#t $r&}t d|Yd}~dSd}~wwxYw)NT) force_readF)without_defaults) main_conf base_conf) overwritez(Can't overwrite local config, reason: %s)rconfig_to_dict normalizerdict_to_config Exceptionloggererror) migratordatabasefakekwargs local_config local_confdefaultsnon_default_confexcs u/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/159_remove_defaults_from_local_config.pymigraters  F"}} !00D0AA ))"u)EE) H    ##$4#EEEEE FFF ?EEEEEEEEEFsA#A++ B5BBc dS)N)rrrrs rrollbackr sD)F) __doc__logging defence360agent.contracts.configr)defence360agent.contracts.config_providerr getLogger__name__rrrrrrr&s 888888DDDDDD  8 $ $FFFF$      rdefence360agent/migrations/__pycache__/160_remove_quarantine.cpython-311.opt-1.pyc0000644000000000000000000001235300000000000024711 0ustar r_j ddlZddlZddlZddlZddlmZddlmZddlmZm Z ddl m Z m Z ddl mZddlmZejeZdZd Zd Zed d gZd ZddZdZde eeffdZdedeeeffdZdS)N)glob)Path)TupleUnion) CharFieldModel) FilenameField) HostingPanelz.imunify.quarantinedz/var/imunify360 quarantinedz/var/wwwz/home*c2Gfddt}|S)zl Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField cZeZdZGfddZedZeZdS)get_model..MalwareHitceZdZdZZdS)"get_model..MalwareHit.Meta malware_hitsN)__name__ __module__ __qualname__db_tabledatabasedbse/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_remove_quarantine.pyMetars%HHHHrF)nullN)rrrrr orig_filerstatusrsr MalwareHitrs`          "Mu--- rr)r)rrs` r get_modelr s? U rFc|rdS|pt}t|}||jt k}|D]8}t |j\} }|| |9tD]B} ttj | tD] } || CdSN)delete_quarantine_folderr selectwherer QUARANTINED find_quarrdelete_instanceQUARANTINE_PARENTSrospathjoin QUAR_NAME) _migratorrfakedelete_function___modelr hitpath_to_deleteparents rmigrater7)s &A)AO h  E,,..&&u|{'BCCK%cm44''' %,,"27<< #B#BCC , ,N ON + + + + ,,,rcdSr")r1r2s rrollbackr:?sDrquarantine_pathct|}|jtkrK||kr5td|t j|ddSdSdS)NzDeleting quarantine folder %sT) ignore_errors)rnamer-resolveloggerinfoshutilrmtree)r;s rr#r#Csr?++O )) 6688 8 8 3_EEE oT:::::: *) 8 8rsourcereturnct|}tttz |tdf}d}d}|jD]P} t j|j}|}n#t$rY>t$r|cYcSwxYw| |j dkr|S| ||z } t|j}n#tt f$r|cYSwxYw ||}n#t"$r|cYSwxYw|tz |fS)zy Find file in quarantine by source path. This function is copied from agent code since it is to be removed. /Nroot)rDEF_QUARr- relative_toparentspwdgetpwuidstatst_uidFileNotFoundErrorKeyErrorpw_namer?r base_home_dirpw_dir RuntimeError ValueError) rDfiledefault_resultuserr6r+resolved_placebase_dirrelatives rr'r'Ms <>// << | ,!--h77  i  ))s<+B  B' B'&B'$&D D! D!%D;; E  E )FN)loggingr*rLrBrpathlibrtypingrrpeeweerr$defence360agent.model.simplificationr +defence360agent.subsys.panels.hosting_panelr getLoggerrr@r-rIr&r)r r7r:strr#r'r9rrres\ ########?>>>>>DDDDDD  8 $ $ "   H5",,,,,   ;eCI.>;;;;'*c'*eD$J/'*'*'*'*'*'*rdefence360agent/migrations/__pycache__/160_remove_quarantine.cpython-311.pyc0000644000000000000000000001235300000000000023752 0ustar r_j ddlZddlZddlZddlZddlmZddlmZddlmZm Z ddl m Z m Z ddl mZddlmZejeZdZd Zd Zed d gZd ZddZdZde eeffdZdedeeeffdZdS)N)glob)Path)TupleUnion) CharFieldModel) FilenameField) HostingPanelz.imunify.quarantinedz/var/imunify360 quarantinedz/var/wwwz/home*c2Gfddt}|S)zl Model stub for migration because we can't use migrator.orm[] due to custom field FilenameField cZeZdZGfddZedZeZdS)get_model..MalwareHitceZdZdZZdS)"get_model..MalwareHit.Meta malware_hitsN)__name__ __module__ __qualname__db_tabledatabasedbse/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_remove_quarantine.pyMetars%HHHHrF)nullN)rrrrr orig_filerstatusrsr MalwareHitrs`          "Mu--- rr)r)rrs` r get_modelr s? U rFc|rdS|pt}t|}||jt k}|D]8}t |j\} }|| |9tD]B} ttj | tD] } || CdSN)delete_quarantine_folderr selectwherer QUARANTINED find_quarrdelete_instanceQUARANTINE_PARENTSrospathjoin QUAR_NAME) _migratorrfakedelete_function___modelr hitpath_to_deleteparents rmigrater7)s &A)AO h  E,,..&&u|{'BCCK%cm44''' %,,"27<< #B#BCC , ,N ON + + + + ,,,rcdSr")r1r2s rrollbackr:?sDrquarantine_pathct|}|jtkrK||kr5td|t j|ddSdSdS)NzDeleting quarantine folder %sT) ignore_errors)rnamer-resolveloggerinfoshutilrmtree)r;s rr#r#Csr?++O )) 6688 8 8 3_EEE oT:::::: *) 8 8rsourcereturnct|}tttz |tdf}d}d}|jD]P} t j|j}|}n#t$rY>t$r|cYcSwxYw| |j dkr|S| ||z } t|j}n#tt f$r|cYSwxYw ||}n#t"$r|cYSwxYw|tz |fS)zy Find file in quarantine by source path. This function is copied from agent code since it is to be removed. /Nroot)rDEF_QUARr- relative_toparentspwdgetpwuidstatst_uidFileNotFoundErrorKeyErrorpw_namer?r base_home_dirpw_dir RuntimeError ValueError) rDfiledefault_resultuserr6r+resolved_placebase_dirrelatives rr'r'Ms <>// << | ,!--h77  i  ))s<+B  B' B'&B'$&D D! D!%D;; E  E )FN)loggingr*rLrBrpathlibrtypingrrpeeweerr$defence360agent.model.simplificationr +defence360agent.subsys.panels.hosting_panelr getLoggerrr@r-rIr&r)r r7r:strr#r'r9rrres\ ########?>>>>>DDDDDD  8 $ $ "   H5",,,,,   ;eCI.>;;;;'*c'*eD$J/'*'*'*'*'*'*rdefence360agent/migrations/__pycache__/160_unmount_sigs_v1.cpython-311.opt-1.pyc0000644000000000000000000000434000000000000024322 0ustar r_jVdZddlZddlZddlmZejeZddZddZ dS)zUnmount sigs/v1 from CageFS.N)PathFc |rdS ddlm}|j}n#t$rd}YnwxYw t |}d|vrdSn@#t $rYdSt$r'}t d||Yd}~dSd}~wwxYw tj d|ddd dS#t$r&}t d |Yd}~dSd}~wwxYw) Nr)clcagefsz/etc/cagefs/cagefs.mpz/var/imunify360/files/sigs/v1zCan't read %s, reason: %sz5sed -i.im360-bak '\:/var/imunify360/files/sigs/v1:d' z && grep /var/imunify360/files/sigs/v1 /proc/mounts | awk '{ print $2 }' | xargs -rn1 umount && /usr/sbin/cagefsctl --wait-lock --unmount-all && /usr/sbin/cagefsctl --wait-lock --force-update-etc && /usr/sbin/cagefsctl --wait-lock --remount-allTz /bin/bash)shell executablez!Can't unmount sigs/v1, reason: %s) defence360agent.subsysrCAGEFS_MP_FILENAME ImportErrorr read_textFileNotFoundError Exceptionlogger exception subprocess check_call)migratordatabasefakekwargsrfilenametextes c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_unmount_sigs_v1.pymigrater s{ +333333. +++*+ H~~'')) +$ 6 6 F 7  4hBBB A DL88  N "  AAAr$s{""  8 $ $"A"A"A"AJ      rdefence360agent/migrations/__pycache__/160_unmount_sigs_v1.cpython-311.pyc0000644000000000000000000000434000000000000023363 0ustar r_jVdZddlZddlZddlmZejeZddZddZ dS)zUnmount sigs/v1 from CageFS.N)PathFc |rdS ddlm}|j}n#t$rd}YnwxYw t |}d|vrdSn@#t $rYdSt$r'}t d||Yd}~dSd}~wwxYw tj d|ddd dS#t$r&}t d |Yd}~dSd}~wwxYw) Nr)clcagefsz/etc/cagefs/cagefs.mpz/var/imunify360/files/sigs/v1zCan't read %s, reason: %sz5sed -i.im360-bak '\:/var/imunify360/files/sigs/v1:d' z && grep /var/imunify360/files/sigs/v1 /proc/mounts | awk '{ print $2 }' | xargs -rn1 umount && /usr/sbin/cagefsctl --wait-lock --unmount-all && /usr/sbin/cagefsctl --wait-lock --force-update-etc && /usr/sbin/cagefsctl --wait-lock --remount-allTz /bin/bash)shell executablez!Can't unmount sigs/v1, reason: %s) defence360agent.subsysrCAGEFS_MP_FILENAME ImportErrorr read_textFileNotFoundError Exceptionlogger exception subprocess check_call)migratordatabasefakekwargsrfilenametextes c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/160_unmount_sigs_v1.pymigrater s{ +333333. +++*+ H~~'')) +$ 6 6 F 7  4hBBB A DL88  N "  AAAr$s{""  8 $ $"A"A"A"AJ      rdefence360agent/migrations/__pycache__/161_remove_ea4_main_local_conf.cpython-311.opt-1.pyc0000644000000000000000000000517400000000000026402 0ustar r_jdZddlZddlmZddlZddlmZejeZ edZ edZ dZ dZ ejd e e fd Zd d ZdS) at Remove /var/cpanel/templates/apache2_4/ea4_main.local file introduced by imunify360. This file was used to change apache log format (%h->%a), when imunify360 installed remote_ip apache module. Since this file is created once it can be outdated after updating cPanel ( in case if /var/cpanel/templates/apache2_4/ea4_main.default also updated). See DEF-9641 for details. N)Path)antivirus_modez./var/cpanel/templates/apache2_4/ea4_main.localz0/var/cpanel/templates/apache2_4/ea4_main.defaultz%a z%h Fc |rdS ddlm}n#t$rYdSwxYw |r|r}|}|tt}||kr3| tj |j dSdSdSdS#t$r'} td|| Yd} ~ dSd} ~ wwxYw)Nr)cPanelzCan't remove %s, reason: %s)im360.subsys.panels.cpanelr ImportError is_installedexists read_textreplaceNEWOLDunlink subprocess check_callREBUILD_HTTPDCONF_CMD Exceptionloggererror) migratordatabasefakedefault_conf_pathlocal_conf_pathkwargsr origin_text restored_textexcs n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/161_remove_ea4_main_local_conf.pymigrater sS 5555555  J     D_%;%;%=%= D+5577K+5577??SIIM ++&&(((%f&BCCCCC D D D D,+ JJJ 2OSIIIIIIIIIJs$ B!C C9C44C9c dS)N)rrrrs rrollbackr#:sD)F)__doc__loggingpathlibrrdefence360agent.utilsr getLogger__name__rEA4_MAIN_LOCAL_PATHEA4_MAIN_DEFAULT_PATHr rskipr r#r"r$rr.s  000000  8 $ $dKLL6   +' JJJJ:      r$defence360agent/migrations/__pycache__/161_remove_ea4_main_local_conf.cpython-311.pyc0000644000000000000000000000517400000000000025443 0ustar r_jdZddlZddlmZddlZddlmZejeZ edZ edZ dZ dZ ejd e e fd Zd d ZdS) at Remove /var/cpanel/templates/apache2_4/ea4_main.local file introduced by imunify360. This file was used to change apache log format (%h->%a), when imunify360 installed remote_ip apache module. Since this file is created once it can be outdated after updating cPanel ( in case if /var/cpanel/templates/apache2_4/ea4_main.default also updated). See DEF-9641 for details. N)Path)antivirus_modez./var/cpanel/templates/apache2_4/ea4_main.localz0/var/cpanel/templates/apache2_4/ea4_main.defaultz%a z%h Fc |rdS ddlm}n#t$rYdSwxYw |r|r}|}|tt}||kr3| tj |j dSdSdSdS#t$r'} td|| Yd} ~ dSd} ~ wwxYw)Nr)cPanelzCan't remove %s, reason: %s)im360.subsys.panels.cpanelr ImportError is_installedexists read_textreplaceNEWOLDunlink subprocess check_callREBUILD_HTTPDCONF_CMD Exceptionloggererror) migratordatabasefakedefault_conf_pathlocal_conf_pathkwargsr origin_text restored_textexcs n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/161_remove_ea4_main_local_conf.pymigrater sS 5555555  J     D_%;%;%=%= D+5577K+5577??SIIM ++&&(((%f&BCCCCC D D D D,+ JJJ 2OSIIIIIIIIIJs$ B!C C9C44C9c dS)N)rrrrs rrollbackr#:sD)F)__doc__loggingpathlibrrdefence360agent.utilsr getLogger__name__rEA4_MAIN_LOCAL_PATHEA4_MAIN_DEFAULT_PATHr rskipr r#r"r$rr.s  000000  8 $ $dKLL6   +' JJJJ:      r$defence360agent/migrations/__pycache__/162_add_resource_type.cpython-311.opt-1.pyc0000644000000000000000000000463200000000000024670 0ustar r_j(xddlZddlZddlmZejdddZejeZ d dZ d dZ dS) N)importerzimav.malwarelib.configMalwareScanResourceType)modulenamedefaultFc |jd}||tjdtjjtjdtj jtjjfgtjdtjdtjdtjd|jd}||tjdtjjtjdtj jtjjfg | |d d dS) N malware_hitsFzresource_type in {})nullr constraintsT)r ) resource_typeapp_namedb_hostdb_portdb_name malware_scans)r total_filestotal_resources) orm add_fieldspw CharFieldrFILEvalueCheckformatDB rename_field)migratordatabasefakekwargs MalwareHits MalwareScans e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/162_add_resource_type.pymigrater%s{,~.K l+06)0036<38>     4((( $''' $''' $''''*,/K l+06)0036<38>     " +}6GHHHHHc L|jd}||ddS)Nr r )r remove_fields)rrr r!r"s r$rollbackr)<s*,~.K ;88888r&)F) loggingpeeweerdefence360agent.utilsrgetr getLogger__name__loggerr%r)r&r$r2s******&(, # "   8 $ $)I)I)I)IX999999r&defence360agent/migrations/__pycache__/162_add_resource_type.cpython-311.pyc0000644000000000000000000000463200000000000023731 0ustar r_j(xddlZddlZddlmZejdddZejeZ d dZ d dZ dS) N)importerzimav.malwarelib.configMalwareScanResourceType)modulenamedefaultFc |jd}||tjdtjjtjdtj jtjjfgtjdtjdtjdtjd|jd}||tjdtjjtjdtj jtjjfg | |d d dS) N malware_hitsFzresource_type in {})nullr constraintsT)r ) resource_typeapp_namedb_hostdb_portdb_name malware_scans)r total_filestotal_resources) orm add_fieldspw CharFieldrFILEvalueCheckformatDB rename_field)migratordatabasefakekwargs MalwareHits MalwareScans e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/162_add_resource_type.pymigrater%s{,~.K l+06)0036<38>     4((( $''' $''' $''''*,/K l+06)0036<38>     " +}6GHHHHHc L|jd}||ddS)Nr r )r remove_fields)rrr r!r"s r$rollbackr)<s*,~.K ;88888r&)F) loggingpeeweerdefence360agent.utilsrgetr getLogger__name__loggerr%r)r&r$r2s******&(, # "   8 $ $)I)I)I)IX999999r&defence360agent/migrations/__pycache__/163_drop_malware_scanned_stat.cpython-311.opt-1.pyc0000644000000000000000000000137700000000000026376 0ustar r_jddZddZdS)Fc n |jd}||dS#t$rYdSwxYw)Nmalware_scanned_stat)orm remove_modelKeyError)migratordatabasefakekwargsmodels m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/163_drop_malware_scanned_stat.pymigrater sP  34e$$$$$      s "& 44c dS)N)rrr r s r rollbackr sDN)F)r rrrr rs7          rdefence360agent/migrations/__pycache__/163_drop_malware_scanned_stat.cpython-311.pyc0000644000000000000000000000137700000000000025437 0ustar r_jddZddZdS)Fc n |jd}||dS#t$rYdSwxYw)Nmalware_scanned_stat)orm remove_modelKeyError)migratordatabasefakekwargsmodels m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/163_drop_malware_scanned_stat.pymigrater sP  34e$$$$$      s "& 44c dS)N)rrr r s r rollbackr sDN)F)r rrrr rs7          rdefence360agent/migrations/__pycache__/164_add_resource_type_to_ignore.cpython-311.opt-1.pyc0000644000000000000000000000513300000000000026734 0ustar r_j{^ddlZddlmZmZmZmZmZGddeZdddZdZ d Z dS) N) CharFieldCheck CompositeKey IntegerFieldModelceZdZGddZdZeZededgZe ddZ dS) TMPMalwareIgnorePathc*eZdZdZeddZdS)TMPMalwareIgnorePath.Metatmp_malware_ignore_pathpath resource_typeN)__name__ __module__ __qualname__db_tabler primary_keyo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/164_add_resource_type_to_ignore.pyMetar s$,"l6?;; rrNFzresource_type in ('file','db'))null constraintscBttjSN)inttimerrrzTMPMalwareIgnorePath.s#dikk:J:Jr)rdefault) rrrrCACHErr rrr added_daterrrr r s<<<<<<<< E 9;;DI 'G!H!H IM52J2JKKKJJJrr F)fakec$t|dSr) change_malware_ignore_path_model)migratorr"___s rmigrater(s$X.....rc|t|d|d|d|ddS)NzyINSERT INTO tmp_malware_ignore_path(path,added_date,resource_type) SELECT path,added_date,'file' FROM malware_ignore_pathzDROP TABLE malware_ignore_pathzAALTER TABLE tmp_malware_ignore_path RENAME TO malware_ignore_pathzUCREATE INDEX malware_ignore_path_resource_type ON malware_ignore_path (resource_type)) create_modelr sql)r%s rr$r$s ./// LL A LL1222 LLK LL 1rcdSrr)r&r's rrollbackr-(sDr) rpeeweerrrrrr r(r$r-rrrr/s FFFFFFFFFFFFFF L L L L L5 L L L %/////         rdefence360agent/migrations/__pycache__/164_add_resource_type_to_ignore.cpython-311.pyc0000644000000000000000000000513300000000000025775 0ustar r_j{^ddlZddlmZmZmZmZmZGddeZdddZdZ d Z dS) N) CharFieldCheck CompositeKey IntegerFieldModelceZdZGddZdZeZededgZe ddZ dS) TMPMalwareIgnorePathc*eZdZdZeddZdS)TMPMalwareIgnorePath.Metatmp_malware_ignore_pathpath resource_typeN)__name__ __module__ __qualname__db_tabler primary_keyo/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/164_add_resource_type_to_ignore.pyMetar s$,"l6?;; rrNFzresource_type in ('file','db'))null constraintscBttjSN)inttimerrrzTMPMalwareIgnorePath.s#dikk:J:Jr)rdefault) rrrrCACHErr rrr added_daterrrr r s<<<<<<<< E 9;;DI 'G!H!H IM52J2JKKKJJJrr F)fakec$t|dSr) change_malware_ignore_path_model)migratorr"___s rmigrater(s$X.....rc|t|d|d|d|ddS)NzyINSERT INTO tmp_malware_ignore_path(path,added_date,resource_type) SELECT path,added_date,'file' FROM malware_ignore_pathzDROP TABLE malware_ignore_pathzAALTER TABLE tmp_malware_ignore_path RENAME TO malware_ignore_pathzUCREATE INDEX malware_ignore_path_resource_type ON malware_ignore_path (resource_type)) create_modelr sql)r%s rr$r$s ./// LL A LL1222 LLK LL 1rcdSrr)r&r's rrollbackr-(sDr) rpeeweerrrrrr r(r$r-rrrr/s FFFFFFFFFFFFFF L L L L L5 L L L %/////         rdefence360agent/migrations/__pycache__/165_add_db_fields_to_malware_history.cpython-311.opt-1.pyc0000644000000000000000000000310200000000000027700 0ustar r_j`ddlmZmZddlmZejdddZddd Zddd ZdS) ) CharFieldCheck)importerzimav.malwarelib.configMalwareScanResourceTypeN)modulenamedefaultF)fakec ,|jd}||tdtdtdt jjt jjfgt jjdS)Nmalware_historyT)nullFzresource_type in {})r constraintsr )app_name resource_type) orm add_fieldsrrformatrDBvalueFILEmigratorr ___r s t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/165_add_db_fields_to_malware_history.pymigrater sl#45O %%%)0036<38> ,06    cN|jd}||dddS)Nr rr)r remove_fieldsrs rrollbackr "s-l#45O ?JHHHHHr) peeweerrdefence360agent.utilsrgetrrr rrr%s########******&(, # "  %,!&IIIIIIIrdefence360agent/migrations/__pycache__/165_add_db_fields_to_malware_history.cpython-311.pyc0000644000000000000000000000310200000000000026741 0ustar r_j`ddlmZmZddlmZejdddZddd Zddd ZdS) ) CharFieldCheck)importerzimav.malwarelib.configMalwareScanResourceTypeN)modulenamedefaultF)fakec ,|jd}||tdtdtdt jjt jjfgt jjdS)Nmalware_historyT)nullFzresource_type in {})r constraintsr )app_name resource_type) orm add_fieldsrrformatrDBvalueFILEmigratorr ___r s t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/165_add_db_fields_to_malware_history.pymigrater sl#45O %%%)0036<38> ,06    cN|jd}||dddS)Nr rr)r remove_fieldsrs rrollbackr "s-l#45O ?JHHHHHr) peeweerrdefence360agent.utilsrgetrrr rrr%s########******&(, # "  %,!&IIIIIIIrdefence360agent/migrations/__pycache__/166_add_id_field_to_malware_ignore_path.cpython-311.opt-1.pyc0000644000000000000000000000442300000000000030332 0ustar r_j\ddlmZddlmZmZmZmZmZGddeZdddZdZ d S) )time) CharFieldCheck IntegerFieldModelPrimaryKeyFieldceZdZGddZdZeZeZede dgZ e ddZ dS) MalwareIgnorePathceZdZdZdZdS)MalwareIgnorePath.Metamalware_ignore_path)))path resource_typeTN)__name__ __module__ __qualname__db_tableindexesw/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/166_add_id_field_to_malware_ignore_path.pyMetar s(6rrNFzresource_type in ('file','db'))null constraintsc8ttSN)intrrrrzMalwareIgnorePath.s#dff++r)rdefault) rrrrCACHEridrrrrr added_daterrrr r s77777777 E   B 9;;DI 'G!H!H IM52E2EFFFJJJrr F)fakec|d|t|d|ddS)NzBALTER TABLE malware_ignore_path RENAME TO malware_ignore_path_old;zINSERT INTO malware_ignore_path(path,added_date,resource_type) SELECT path,added_date,resource_type FROM malware_ignore_path_oldz#DROP TABLE malware_ignore_path_old;)sql create_modelr )migratorr#___s rmigrater*si LLL +,,, LL L LL677777rcdSrr)r(r)s rrollbackr,!sDrN) rpeeweerrrrrr r*r,rrrr.sIIIIIIIIIIIIII G G G G G G G G % 8 8 8 8 8     rdefence360agent/migrations/__pycache__/166_add_id_field_to_malware_ignore_path.cpython-311.pyc0000644000000000000000000000442300000000000027373 0ustar r_j\ddlmZddlmZmZmZmZmZGddeZdddZdZ d S) )time) CharFieldCheck IntegerFieldModelPrimaryKeyFieldceZdZGddZdZeZeZede dgZ e ddZ dS) MalwareIgnorePathceZdZdZdZdS)MalwareIgnorePath.Metamalware_ignore_path)))path resource_typeTN)__name__ __module__ __qualname__db_tableindexesw/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/166_add_id_field_to_malware_ignore_path.pyMetar s(6rrNFzresource_type in ('file','db'))null constraintsc8ttSN)intrrrrzMalwareIgnorePath.s#dff++r)rdefault) rrrrCACHEridrrrrr added_daterrrr r s77777777 E   B 9;;DI 'G!H!H IM52E2EFFFJJJrr F)fakec|d|t|d|ddS)NzBALTER TABLE malware_ignore_path RENAME TO malware_ignore_path_old;zINSERT INTO malware_ignore_path(path,added_date,resource_type) SELECT path,added_date,resource_type FROM malware_ignore_path_oldz#DROP TABLE malware_ignore_path_old;)sql create_modelr )migratorr#___s rmigrater*si LLL +,,, LL L LL677777rcdSrr)r(r)s rrollbackr,!sDrN) rpeeweerrrrrr r*r,rrrr.sIIIIIIIIIIIIII G G G G G G G G % 8 8 8 8 8     rdefence360agent/migrations/__pycache__/167_remote_iplist.cpython-311.opt-1.pyc0000644000000000000000000000462400000000000024055 0ustar r_jfddlmZmZmZmZGddeZGddeZd dZd dZd S) ) CharFieldModel IntegerField CompositeKeyceZdZedZedZedZedZGddZdS) IPListRecordFnullc.eZdZdZeddddZdS)IPListRecord.Meta iplistrecordnetwork_addressnetmaskversion iplist_idN__name__ __module__ __qualname__db_tabler primary_keya/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/167_remote_iplist.pyMetar s-!"l y)[  rrN) rrrrrrrrrrrrrrs"l...Ol&&&Gl&&&G %(((I          rrcXeZdZedZedZGddZdS) IPListPurposeFr c*eZdZdZeddZdS)IPListPurpose.Meta iplistpurposepurposerNrrrrrrs$""l9k:: rrN)rrrrr!rrrrrrrrs`iU###G %(((I;;;;;;;;;;rrFc n|t|tdS)N) create_modelrr)migratordatabasefakekwargss rmigrater(s0 ,''' -(((((rc |jd}|||jd}||dS)Nr r )orm remove_model)r$r%r&r'rrs rrollbackr,sH</L ,'''L1M -(((((rN)F) peeweerrrrrrr(r,rrrr.s????????????      5    ;;;;;E;;;)))) ))))))rdefence360agent/migrations/__pycache__/167_remote_iplist.cpython-311.pyc0000644000000000000000000000462400000000000023116 0ustar r_jfddlmZmZmZmZGddeZGddeZd dZd dZd S) ) CharFieldModel IntegerField CompositeKeyceZdZedZedZedZedZGddZdS) IPListRecordFnullc.eZdZdZeddddZdS)IPListRecord.Meta iplistrecordnetwork_addressnetmaskversion iplist_idN__name__ __module__ __qualname__db_tabler primary_keya/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/167_remote_iplist.pyMetar s-!"l y)[  rrN) rrrrrrrrrrrrrrs"l...Ol&&&Gl&&&G %(((I          rrcXeZdZedZedZGddZdS) IPListPurposeFr c*eZdZdZeddZdS)IPListPurpose.Meta iplistpurposepurposerNrrrrrrs$""l9k:: rrN)rrrrr!rrrrrrrrs`iU###G %(((I;;;;;;;;;;rrFc n|t|tdS)N) create_modelrr)migratordatabasefakekwargss rmigrater(s0 ,''' -(((((rc |jd}|||jd}||dS)Nr r )orm remove_model)r$r%r&r'rrs rrollbackr,sH</L ,'''L1M -(((((rN)F) peeweerrrrrrr(r,rrrr.s????????????      5    ;;;;;E;;;)))) ))))))rdefence360agent/migrations/__pycache__/168_add_icontact_throttle.cpython-311.opt-1.pyc0000644000000000000000000000355300000000000025540 0ustar r_jpVddlmZmZmZmZddlmZGddeZd dZd dZ dS) ) CharFieldCheck IntegerFieldModel)IContactMessageTypec eZdZGddZededeej eej fgZ e dZ dS) IContactThrottleceZdZdZdS)IContactThrottle.Metaicontact_throttleN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/168_add_icontact_throttle.pyMetar s&rrTzmessage_type in {}) primary_key constraintsr)defaultN)r rrrrrformatstrr MALWARE_FOUNDSCAN_NOT_SCHEDULED message_typer timestamprrrr r s''''''''9 E$++/=>>/BCC       L Q'''IIIrr Fc :|tdS)N) create_modelr )migratordatabasefakekwargss rmigrater$s *+++++rc J|jd}||dS)Nr )orm remove_model)r r!r"r#r s rrollbackr(s+|$78 *+++++rN)F) peeweerrrr defence360agent.contracts.configrr r$r(rrrr+s888888888888@@@@@@(((((u((((,,,,,,,,,,rdefence360agent/migrations/__pycache__/168_add_icontact_throttle.cpython-311.pyc0000644000000000000000000000355300000000000024601 0ustar r_jpVddlmZmZmZmZddlmZGddeZd dZd dZ dS) ) CharFieldCheck IntegerFieldModel)IContactMessageTypec eZdZGddZededeej eej fgZ e dZ dS) IContactThrottleceZdZdZdS)IContactThrottle.Metaicontact_throttleN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/168_add_icontact_throttle.pyMetar s&rrTzmessage_type in {}) primary_key constraintsr)defaultN)r rrrrrformatstrr MALWARE_FOUNDSCAN_NOT_SCHEDULED message_typer timestamprrrr r s''''''''9 E$++/=>>/BCC       L Q'''IIIrr Fc :|tdS)N) create_modelr )migratordatabasefakekwargss rmigrater$s *+++++rc J|jd}||dS)Nr )orm remove_model)r r!r"r#r s rrollbackr(s+|$78 *+++++rN)F) peeweerrrr defence360agent.contracts.configrr r$r(rrrr+s888888888888@@@@@@(((((u((((,,,,,,,,,,r././@LongLink0000644000000000000000000000016100000000000007771 Lustar defence360agent/migrations/__pycache__/169_add_record_to_throttle_scan_not_schedule_events.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/169_add_record_to_throttle_scan_not_schedule_events.cpython-30000644000000000000000000000245700000000000031150 0ustar r_j{*ddlZddlmZddZddZdS)N)IContactMessageTypeFc |rdS|jd}|tjt jdzdS)Nicontact_throttlei: ) message_type timestamp)ormcreaterSCAN_NOT_SCHEDULEDtime)migratordatabasefakekwargsIContactThrotles /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.pymigratersW l#67O(;)+++c |rdS|jd}||jtjkdS)Nr)rdeletewhererrr execute)r r rrIContactThrottles rrollbackrsZ |$78##%)<)OO giiiiir)F)r defence360agent.contracts.configrrrrrrsR @@@@@@r././@LongLink0000644000000000000000000000015300000000000007772 Lustar defence360agent/migrations/__pycache__/169_add_record_to_throttle_scan_not_schedule_events.cpython-311.pycdefence360agent/migrations/__pycache__/169_add_record_to_throttle_scan_not_schedule_events.cpython-30000644000000000000000000000245700000000000031150 0ustar r_j{*ddlZddlmZddZddZdS)N)IContactMessageTypeFc |rdS|jd}|tjt jdzdS)Nicontact_throttlei: ) message_type timestamp)ormcreaterSCAN_NOT_SCHEDULEDtime)migratordatabasefakekwargsIContactThrotles /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.pymigratersW l#67O(;)+++c |rdS|jd}||jtjkdS)Nr)rdeletewhererrr execute)r r rrIContactThrottles rrollbackrsZ |$78##%)<)OO giiiiir)F)r defence360agent.contracts.configrrrrrrsR @@@@@@rdefence360agent/migrations/__pycache__/170_add_db_fields_to_malware_history.cpython-311.opt-1.pyc0000644000000000000000000000207500000000000027704 0ustar r_j*ddlmZdddZdddZdS)) CharFieldF)fakec|jd}||tdtdtddS)Nmalware_historyT)null)db_hostdb_portdb_name)orm add_fieldsrmigratorr___rs t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/170_add_db_fields_to_malware_history.pymigraterscl#45O t$$$t$$$t$$$ cP|jd}||ddddS)Nrrr r )r remove_fieldsr s rrollbackrs/l#45O ?Iy)LLLLLrN)peeweerrrrrrsd %!&MMMMMMMrdefence360agent/migrations/__pycache__/170_add_db_fields_to_malware_history.cpython-311.pyc0000644000000000000000000000207500000000000026745 0ustar r_j*ddlmZdddZdddZdS)) CharFieldF)fakec|jd}||tdtdtddS)Nmalware_historyT)null)db_hostdb_portdb_name)orm add_fieldsrmigratorr___rs t/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/170_add_db_fields_to_malware_history.pymigraterscl#45O t$$$t$$$t$$$ cP|jd}||ddddS)Nrrr r )r remove_fieldsr s rrollbackrs/l#45O ?Iy)LLLLLrN)peeweerrrrrrsd %!&MMMMMMMrdefence360agent/migrations/__pycache__/180_move_captcha_configs.cpython-311.opt-1.pyc0000644000000000000000000000122400000000000025323 0ustar r_jdZddZddZdS)z No need to rollback captcha keys config because WebshieldCaptchaKeys plugin recreates it on the agent start so just stubbing the migration Fc dSNmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/180_move_captcha_configs.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r defence360agent/migrations/__pycache__/180_move_captcha_configs.cpython-311.pyc0000644000000000000000000000122400000000000024364 0ustar r_jdZddZddZdS)z No need to rollback captcha keys config because WebshieldCaptchaKeys plugin recreates it on the agent start so just stubbing the migration Fc dSNmigratordatabasefakekwargss h/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/180_move_captcha_configs.pymigrater Dc dSrrrs r rollbackr r r N)F)__doc__r rrr r rsA           r ././@LongLink0000644000000000000000000000015300000000000007772 Lustar defence360agent/migrations/__pycache__/182_remove_constraints_from_icontact_throttle.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/182_remove_constraints_from_icontact_throttle.cpython-311.opt0000644000000000000000000000361300000000000031020 0ustar r_jJdZddlmZmZmZGddeZd dZd dZdS) zl Drop constrains for icontact_throttle.message_type, since correlation server can set any type (DEF-19971). ) CharField IntegerFieldModelcXeZdZGddZedZedZdS)IContactThrottleceZdZdZdS)IContactThrottle.Metaicontact_throttleN)__name__ __module__ __qualname__db_table}/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/182_remove_constraints_from_icontact_throttle.pyMetar s&rrT) primary_keyr)defaultN)r r r rr message_typer timestamprrrrrs\''''''''9...L Q'''IIIrrFc |rdS|d|t|d|ddS)Nz=ALTER TABLE icontact_throttle RENAME TO icontact_throttle_oldznINSERT INTO icontact_throttle(message_type,timestamp) SELECT message_type,timestamp FROM icontact_throttle_oldz DROP TABLE icontact_throttle_old)sql create_modelrmigratordatabasefakekwargss rmigratersv  LLG *+++ LL C LL344444rc dS)Nrrs rrollbackr!sDrN)F)__doc__peeweerrrrrr!rrrr$s2111111111(((((u((( 5 5 5 5      rdefence360agent/migrations/__pycache__/182_remove_constraints_from_icontact_throttle.cpython-311.pyc0000644000000000000000000000361300000000000031011 0ustar r_jJdZddlmZmZmZGddeZd dZd dZdS) zl Drop constrains for icontact_throttle.message_type, since correlation server can set any type (DEF-19971). ) CharField IntegerFieldModelcXeZdZGddZedZedZdS)IContactThrottleceZdZdZdS)IContactThrottle.Metaicontact_throttleN)__name__ __module__ __qualname__db_table}/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/182_remove_constraints_from_icontact_throttle.pyMetar s&rrT) primary_keyr)defaultN)r r r rr message_typer timestamprrrrrs\''''''''9...L Q'''IIIrrFc |rdS|d|t|d|ddS)Nz=ALTER TABLE icontact_throttle RENAME TO icontact_throttle_oldznINSERT INTO icontact_throttle(message_type,timestamp) SELECT message_type,timestamp FROM icontact_throttle_oldz DROP TABLE icontact_throttle_old)sql create_modelrmigratordatabasefakekwargss rmigratersv  LLG *+++ LL C LL344444rc dS)Nrrs rrollbackr!sDrN)F)__doc__peeweerrrrrr!rrrr$s2111111111(((((u((( 5 5 5 5      rdefence360agent/migrations/__pycache__/183_add_user_field_to_malware_scans.cpython-311.opt-1.pyc0000644000000000000000000000174100000000000027523 0ustar r_j*ddlmZdddZdddZdS)) CharFieldF)fakecr|rdS|jd}||tddS)N malware_scansT)null) initiator)orm add_fieldsrmigratorr___rs s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/183_add_user_field_to_malware_scans.pymigratersS L1M &&&cT|rdS|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs7 L1M =+66666rN)peeweerrrrrrs] %!&7777777rdefence360agent/migrations/__pycache__/183_add_user_field_to_malware_scans.cpython-311.pyc0000644000000000000000000000174100000000000026564 0ustar r_j*ddlmZdddZdddZdS)) CharFieldF)fakecr|rdS|jd}||tddS)N malware_scansT)null) initiator)orm add_fieldsrmigratorr___rs s/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/183_add_user_field_to_malware_scans.pymigratersS L1M &&&cT|rdS|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs7 L1M =+66666rN)peeweerrrrrrs] %!&7777777r././@LongLink0000644000000000000000000000015400000000000007773 Lustar defence360agent/migrations/__pycache__/184_create_a_table_for_secure_site_permissions.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/184_create_a_table_for_secure_site_permissions.cpython-311.op0000644000000000000000000000270400000000000030663 0ustar r_jBddlZGddejZddZdZdS)NceZdZGddZejdZejdejdgdZ d S) SecureSiteceZdZdZdS)SecureSite.Meta secure_siteN)__name__ __module__ __qualname__db_table~/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/184_create_a_table_for_secure_site_permissions.pyMetars r rT)uniqueFz$subscription_type in ('basic','pro')basic)null constraintsdefaultN) rr r rpw CharFielduser TextFieldChecksubscription_typer r rrrs!!!!!!!! 2r'sj        &&&&r ././@LongLink0000644000000000000000000000014600000000000007774 Lustar defence360agent/migrations/__pycache__/184_create_a_table_for_secure_site_permissions.cpython-311.pycdefence360agent/migrations/__pycache__/184_create_a_table_for_secure_site_permissions.cpython-311.py0000644000000000000000000000270400000000000030675 0ustar r_jBddlZGddejZddZdZdS)NceZdZGddZejdZejdejdgdZ d S) SecureSiteceZdZdZdS)SecureSite.Meta secure_siteN)__name__ __module__ __qualname__db_table~/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/184_create_a_table_for_secure_site_permissions.pyMetars r rT)uniqueFz$subscription_type in ('basic','pro')basic)null constraintsdefaultN) rr r rpw CharFielduser TextFieldChecksubscription_typer r rrrs!!!!!!!! 2r'sj        &&&&r defence360agent/migrations/__pycache__/185_delete_all_secure_site_id.cpython-311.opt-1.pyc0000644000000000000000000000242100000000000026327 0ustar r_jHddlZddlmZejeZddZdZdS)N)PathFc |rdS tdd}|D],}|s|d-dS#t$rt dYdSwxYw)N/zhome*/*/.secure_site_idT) missing_okz:An exception occurred while deleting .secure_site_id files)rglob is_symlinkunlink Exceptionlogger exception)migrator_dbfake__id_filesid_files m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/185_delete_all_secure_site_id.pymigraters  99>>";<< 0 0G%%'' 0$/// 0 0     H       sAA$BBcdS)z Not supportedN)_rs rrollbackrs)F)loggingpathlibr getLogger__name__r rrrrrrs_  8 $ $     rdefence360agent/migrations/__pycache__/185_delete_all_secure_site_id.cpython-311.pyc0000644000000000000000000000242100000000000025370 0ustar r_jHddlZddlmZejeZddZdZdS)N)PathFc |rdS tdd}|D],}|s|d-dS#t$rt dYdSwxYw)N/zhome*/*/.secure_site_idT) missing_okz:An exception occurred while deleting .secure_site_id files)rglob is_symlinkunlink Exceptionlogger exception)migrator_dbfake__id_filesid_files m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/185_delete_all_secure_site_id.pymigraters  99>>";<< 0 0G%%'' 0$/// 0 0     H       sAA$BBcdS)z Not supportedN)_rs rrollbackrs)F)loggingpathlibr getLogger__name__r rrrrrrs_  8 $ $     rdefence360agent/migrations/__pycache__/186_add_user_field_to_icontact_throttle.cpython-311.opt-1.pyc0000644000000000000000000000634600000000000030446 0ustar r_j>6ddlmZmZmZmZdddZdddZdS)) CompositeKeyModel CharField IntegerFieldF)fakec@|rdS|jd}Gddt}||td|||d|d|d dS) Nicontact_throttlecleZdZGddZeZedZedZdS)$migrate..TmpIContactThrottlec*eZdZdZeddZdS))migrate..TmpIContactThrottle.Metatmp_icontact_throttle message_typeuserN)__name__ __module__ __qualname__db_tabler primary_keyw/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/186_add_user_field_to_icontact_throttle.pyMetar s$.H&,~v>>KKKrrTnullrdefaultN) rrrrrrrr timestamprrrTmpIContactThrottler sg ? ? ? ? ? ? ? ?!y{{ yd### L+++ rrTr)rz}INSERT INTO tmp_icontact_throttle (message_type, user, timestamp) SELECT message_type, user, timestamp FROM icontact_throttleDROP TABLE icontact_throttle=ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle)ormr add_fieldsr create_modelsql)migratorr___r rs rmigrater)s  %89,,,,,e,,,  D ! ! !  -... LL F LL/000 LLGrc|rdSGddt}|||d|d|ddS)NcXeZdZGddZedZedZdS)%rollback..TmpIContactThrottleceZdZdZdS)*rollback..TmpIContactThrottle.Metar N)rrrrrrrrr.)s*HHHrrT)rrrN)rrrrrrrrrrrrr,(s\ + + + + + + + +!yT222  L+++ rrzqINSERT INTO tmp_icontact_throttle (message_type, timestamp) SELECT message_type, timestamp FROM icontact_throttler r!)rr$r%)r&rr'r(rs rrollbackr/$s ,,,,,e,,, -... LL @ LL/000 LLGrN)peeweerrrrr)r/rrrr1sp???????????? %@!&rdefence360agent/migrations/__pycache__/186_add_user_field_to_icontact_throttle.cpython-311.pyc0000644000000000000000000000634600000000000027507 0ustar r_j>6ddlmZmZmZmZdddZdddZdS)) CompositeKeyModel CharField IntegerFieldF)fakec@|rdS|jd}Gddt}||td|||d|d|d dS) Nicontact_throttlecleZdZGddZeZedZedZdS)$migrate..TmpIContactThrottlec*eZdZdZeddZdS))migrate..TmpIContactThrottle.Metatmp_icontact_throttle message_typeuserN)__name__ __module__ __qualname__db_tabler primary_keyw/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/186_add_user_field_to_icontact_throttle.pyMetar s$.H&,~v>>KKKrrTnullrdefaultN) rrrrrrrr timestamprrrTmpIContactThrottler sg ? ? ? ? ? ? ? ?!y{{ yd### L+++ rrTr)rz}INSERT INTO tmp_icontact_throttle (message_type, user, timestamp) SELECT message_type, user, timestamp FROM icontact_throttleDROP TABLE icontact_throttle=ALTER TABLE tmp_icontact_throttle RENAME TO icontact_throttle)ormr add_fieldsr create_modelsql)migratorr___r rs rmigrater)s  %89,,,,,e,,,  D ! ! !  -... LL F LL/000 LLGrc|rdSGddt}|||d|d|ddS)NcXeZdZGddZedZedZdS)%rollback..TmpIContactThrottleceZdZdZdS)*rollback..TmpIContactThrottle.Metar N)rrrrrrrrr.)s*HHHrrT)rrrN)rrrrrrrrrrrrr,(s\ + + + + + + + +!yT222  L+++ rrzqINSERT INTO tmp_icontact_throttle (message_type, timestamp) SELECT message_type, timestamp FROM icontact_throttler r!)rr$r%)r&rr'r(rs rrollbackr/$s ,,,,,e,,, -... LL @ LL/000 LLGrN)peeweerrrrr)r/rrrr1sp???????????? %@!&rdefence360agent/migrations/__pycache__/187_fix_scan_unserialization.cpython-311.opt-1.pyc0000644000000000000000000000712500000000000026271 0ustar r_jdZddlZddlZddlZddlmZejeZedZ dZ dZ Gddej Z d Zd d d Zd d d ZdS)zZ Used to fix issue with inability to unserialize stored scans. See DEF-23121 for details. N)Pathz$/var/imunify360/aibolit/scans.picklezim360.malwarelibzimav.malwarelibceZdZfdZxZS) AVUnpicklerc$ t||S#t$r`|trDt j|tt}t||cYSwxYwN) super find_classModuleNotFoundError startswithIM360_MALWARELIB importlib import_modulereplace AV_MALWARELIBgetattr)selfmodulename av_module __class__s l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/187_fix_scan_unserialization.pyr zAVUnpickler.find_classs 77%%fd33 3"     !122 0%3NN#3]CC y$/////   s!%A&B B)__name__ __module__ __qualname__r __classcell__)rs@rrrs8         rc||jdz}|d5}tj||dddn #1swxYwY||dS)Nz.tempwb) with_nameropenpickledumpr)objpath temp_pathfs rr"r"sty7233I    CdsAAAF)fakec|stsdSttvr td5}t |}dddn #1swxYwYt|tdS#t$r,}t dt|Yd}~dSd}~wwxYwdS)Nrbz"Failed to load pickle scans %s: %s) SCANS_PATHexistsr encode read_bytesr rloadr" Exceptionlogger exception)migratorr'___r&r#excs rmigrater6'sC :$$&&  J$9$9$;$;;; "&& ,!!!nn))++ , , , , , , , , , , , , , , , j ! ! ! ! !       4j#           <;s<B;+"B B;BB; B!B;; C1!C,,C1cdSr)r2r'r3r4s rrollbackr97sDr)__doc__r loggingr!pathlibr getLoggerrr0r*r r Unpicklerrr"r6r9r8rrr?s   8 $ $ T8 9 9 %!      &"    % " " " " " !&       rdefence360agent/migrations/__pycache__/187_fix_scan_unserialization.cpython-311.pyc0000644000000000000000000000712500000000000025332 0ustar r_jdZddlZddlZddlZddlmZejeZedZ dZ dZ Gddej Z d Zd d d Zd d d ZdS)zZ Used to fix issue with inability to unserialize stored scans. See DEF-23121 for details. N)Pathz$/var/imunify360/aibolit/scans.picklezim360.malwarelibzimav.malwarelibceZdZfdZxZS) AVUnpicklerc$ t||S#t$r`|trDt j|tt}t||cYSwxYwN) super find_classModuleNotFoundError startswithIM360_MALWARELIB importlib import_modulereplace AV_MALWARELIBgetattr)selfmodulename av_module __class__s l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/187_fix_scan_unserialization.pyr zAVUnpickler.find_classs 77%%fd33 3"     !122 0%3NN#3]CC y$/////   s!%A&B B)__name__ __module__ __qualname__r __classcell__)rs@rrrs8         rc||jdz}|d5}tj||dddn #1swxYwY||dS)Nz.tempwb) with_nameropenpickledumpr)objpath temp_pathfs rr"r"sty7233I    CdsAAAF)fakec|stsdSttvr td5}t |}dddn #1swxYwYt|tdS#t$r,}t dt|Yd}~dSd}~wwxYwdS)Nrbz"Failed to load pickle scans %s: %s) SCANS_PATHexistsr encode read_bytesr rloadr" Exceptionlogger exception)migratorr'___r&r#excs rmigrater6'sC :$$&&  J$9$9$;$;;; "&& ,!!!nn))++ , , , , , , , , , , , , , , , j ! ! ! ! !       4j#           <;s<B;+"B B;BB; B!B;; C1!C,,C1cdSr)r2r'r3r4s rrollbackr97sDr)__doc__r loggingr!pathlibr getLoggerrr0r*r r Unpicklerrr"r6r9r8rrr?s   8 $ $ T8 9 9 %!      &"    % " " " " " !&       r././@LongLink0000644000000000000000000000014700000000000007775 Lustar defence360agent/migrations/__pycache__/188_add_protection_status_field_myimunify.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/188_add_protection_status_field_myimunify.cpython-311.opt-1.p0000644000000000000000000000260100000000000030510 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) BooleanField CharFieldModelcZeZdZGddZedZeddZdS) MyImunifyceZdZdZdS)MyImunify.Meta myimunifyN)__name__ __module__ __qualname__db_tabley/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/188_add_protection_status_field_myimunify.pyMetar srrT)uniqueF)nulldefaultN)r r r rruserr protectionrrrrrs^ 9D ! ! !D5%888JJJrrFc B|rdS|tdSN) create_modelrmigrator_dbfake__s rmigrater )  )$$$$$rc B|rdS|tdSr) remove_modelrrs rrollbackr$r!rN)F)peeweerrrrr r$rrrr&s111111111199999999%%%% %%%%%%rdefence360agent/migrations/__pycache__/188_add_protection_status_field_myimunify.cpython-311.pyc0000644000000000000000000000260100000000000030105 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) BooleanField CharFieldModelcZeZdZGddZedZeddZdS) MyImunifyceZdZdZdS)MyImunify.Meta myimunifyN)__name__ __module__ __qualname__db_tabley/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/188_add_protection_status_field_myimunify.pyMetar srrT)uniqueF)nulldefaultN)r r r rruserr protectionrrrrrs^ 9D ! ! !D5%888JJJrrFc B|rdS|tdSN) create_modelrmigrator_dbfake__s rmigrater )  )$$$$$rc B|rdS|tdSr) remove_modelrrs rrollbackr$r!rN)F)peeweerrrrr r$rrrr&s111111111199999999%%%% %%%%%%rdefence360agent/migrations/__pycache__/189_add_messages_to_send_nr.cpython-311.opt-1.pyc0000644000000000000000000000266700000000000026040 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) FloatFieldModel BlobFieldcXeZdZGddZedZedZdS) MessageToSendceZdZdZdS)MessageToSend.Metamessages_to_send_nrN)__name__ __module__ __qualname__db_tablek/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/189_add_messages_to_send_nr.pyMetar s(rrF)nullN)r r r rr timestamprmessagerrrrrs\)))))))) &&&IiU###GGGrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s -(((((rc J|jd}||dS)Nmessages_to_send)orm drop_model)rrrrrs rrollbackr!s)L!34M  &&&&&rN)F)peeweerrrrrr!rrrr#s//////////$$$$$E$$$))))''''''rdefence360agent/migrations/__pycache__/189_add_messages_to_send_nr.cpython-311.pyc0000644000000000000000000000266700000000000025101 0ustar r_jFddlmZmZmZGddeZddZddZdS) ) FloatFieldModel BlobFieldcXeZdZGddZedZedZdS) MessageToSendceZdZdZdS)MessageToSend.Metamessages_to_send_nrN)__name__ __module__ __qualname__db_tablek/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/189_add_messages_to_send_nr.pyMetar s(rrF)nullN)r r r rr timestamprmessagerrrrrs\)))))))) &&&IiU###GGGrrFc :|tdS)N) create_modelr)migratordatabasefakekwargss rmigrater s -(((((rc J|jd}||dS)Nmessages_to_send)orm drop_model)rrrrrs rrollbackr!s)L!34M  &&&&&rN)F)peeweerrrrrr!rrrr#s//////////$$$$$E$$$))))''''''rdefence360agent/migrations/__pycache__/190_add_analyst_cleanup_request_table.cpython-311.opt-1.pyc0000644000000000000000000000453600000000000030105 0ustar r_jxbddlmZmZmZmZmZmZddlmZmZGddeZ d dZ d dZ dS) )Model AutoField CharField TextFieldTimestampFieldCheck)datetimetimezonec8eZdZdZGddZeZedZedZ e dZ e de jejZeddedg Ze de jejZd S) AnalystCleanupRequestz Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. ceZdZdZdS)AnalystCleanupRequest.Metaanalyst_cleanup_requestsN)__name__ __module__ __qualname__db_tableu/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/190_add_analyst_cleanup_request_table.pyMetars-rrF)null)rdefaultpendingz/status in ('pending','in_progress','completed'))rr constraintsN)rrr__doc__rridrusername zendesk_idr ticket_linkrr nowr utc created_atrstatus last_updatedrrrr r s ........ Bye$$$H&&&J)'''KULHL4N4NOOOJ Y ULMMNF "> LHL66LLLrr Fc :|tdS)N) create_modelr )migratordatabasefakekwargss rmigrater,$s /00000rc J|jd}||dS)Nr)orm drop_model)r(r)r*r+analyst_cleanup_requests rrollbackr1(s+&l+EF /00000rN)F) peeweerrrrrrr r r r,r1rrrr3s('''''''E01111111111rdefence360agent/migrations/__pycache__/190_add_analyst_cleanup_request_table.cpython-311.pyc0000644000000000000000000000453600000000000027146 0ustar r_jxbddlmZmZmZmZmZmZddlmZmZGddeZ d dZ d dZ dS) )Model AutoField CharField TextFieldTimestampFieldCheck)datetimetimezonec8eZdZdZGddZeZedZedZ e dZ e de jejZeddedg Ze de jejZd S) AnalystCleanupRequestz Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. ceZdZdZdS)AnalystCleanupRequest.Metaanalyst_cleanup_requestsN)__name__ __module__ __qualname__db_tableu/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/190_add_analyst_cleanup_request_table.pyMetars-rrF)null)rdefaultpendingz/status in ('pending','in_progress','completed'))rr constraintsN)rrr__doc__rridrusername zendesk_idr ticket_linkrr nowr utc created_atrstatus last_updatedrrrr r s ........ Bye$$$H&&&J)'''KULHL4N4NOOOJ Y ULMMNF "> LHL66LLLrr Fc :|tdS)N) create_modelr )migratordatabasefakekwargss rmigrater,$s /00000rc J|jd}||dS)Nr)orm drop_model)r(r)r*r+analyst_cleanup_requests rrollbackr1(s+&l+EF /00000rN)F) peeweerrrrrrr r r r,r1rrrr3s('''''''E01111111111rdefence360agent/migrations/__pycache__/191_create_wordpress_incident_table.cpython-311.opt-1.pyc0000644000000000000000000000522200000000000027565 0ustar r_j6TdZddlZddlmZGddejZd dZd dZdS) zCreate wordpress_incident table for WordPress CVE protection incidents. This migration creates a dedicated table for WordPress incidents rather than using the generic incident table. This allows for better separation of concerns and cleaner data model. N) JSONFieldceZdZejddZejdZejdZej dZ ejdZ ejdZ ejdZ ejdZejdZejddZejddZedZejddZGd d ZdS) WordpressIncidentT) primary_keynull)r country_id)r column_nameN)rdefaultFceZdZdZdS)WordpressIncident.Metawordpress_incidentN)__name__ __module__ __qualname__db_tables/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/191_create_wordpress_incident_table.pyMetar s'rr)rrrpw IntegerFieldid CharFieldpluginrule FloatField timestampretriesseverityname TextField descriptionabusercountrydomainr extra_info BooleanFieldsent_to_serverrrrrrr sF T 5 5 5B R\t $ $ $F 2|tddS)NT)cascade) remove_modelrr-s rrollbackr6&s" +T:::::r)F) __doc__peeweerplayhouse.sqlite_extrModelrr2r6rrrr;s******((((((((&EEEE;;;;;;rdefence360agent/migrations/__pycache__/191_create_wordpress_incident_table.cpython-311.pyc0000644000000000000000000000522200000000000026626 0ustar r_j6TdZddlZddlmZGddejZd dZd dZdS) zCreate wordpress_incident table for WordPress CVE protection incidents. This migration creates a dedicated table for WordPress incidents rather than using the generic incident table. This allows for better separation of concerns and cleaner data model. N) JSONFieldceZdZejddZejdZejdZej dZ ejdZ ejdZ ejdZ ejdZejdZejddZejddZedZejddZGd d ZdS) WordpressIncidentT) primary_keynull)r country_id)r column_nameN)rdefaultFceZdZdZdS)WordpressIncident.Metawordpress_incidentN)__name__ __module__ __qualname__db_tables/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/191_create_wordpress_incident_table.pyMetar s'rr)rrrpw IntegerFieldid CharFieldpluginrule FloatField timestampretriesseverityname TextField descriptionabusercountrydomainr extra_info BooleanFieldsent_to_serverrrrrrr sF T 5 5 5B R\t $ $ $F 2|tddS)NT)cascade) remove_modelrr-s rrollbackr6&s" +T:::::r)F) __doc__peeweerplayhouse.sqlite_extrModelrr2r6rrrr;s******((((((((&EEEE;;;;;;rdefence360agent/migrations/__pycache__/192_add_wordpress_incident_unique_index.cpython-311.opt-1.pyc0000644000000000000000000000255000000000000030462 0ustar r_jSdZddZddZdS)aAdd unique composite index to wordpress_incident table for deduplication. This migration adds a unique index on the fields used to identify duplicate incidents (abuser, name, plugin, rule, severity, domain), similar to the aggregation key used in the resident agent's aggregate plugin. Fc Z|jd}||ddddddd d S) z6Add unique composite index for incident deduplication.wordpress_incidentabusernamepluginruleseveritydomainT)uniqueN)orm add_indexmigratordatabasefakekwargsWordpressIncidents w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/192_add_wordpress_incident_unique_index.pymigrater sQ %9:      c V|jd}||dddddddS) z"Remove the unique composite index.rrrrrrr N)r drop_indexr s rrollbackrsG %9: rN)F)__doc__rrrrrsA$      rdefence360agent/migrations/__pycache__/192_add_wordpress_incident_unique_index.cpython-311.pyc0000644000000000000000000000255000000000000027523 0ustar r_jSdZddZddZdS)aAdd unique composite index to wordpress_incident table for deduplication. This migration adds a unique index on the fields used to identify duplicate incidents (abuser, name, plugin, rule, severity, domain), similar to the aggregation key used in the resident agent's aggregate plugin. Fc Z|jd}||ddddddd d S) z6Add unique composite index for incident deduplication.wordpress_incidentabusernamepluginruleseveritydomainT)uniqueN)orm add_indexmigratordatabasefakekwargsWordpressIncidents w/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/192_add_wordpress_incident_unique_index.pymigrater sQ %9:      c V|jd}||dddddddS) z"Remove the unique composite index.rrrrrrr N)r drop_indexr s rrollbackrsG %9: rN)F)__doc__rrrrrsA$      r././@LongLink0000644000000000000000000000015700000000000007776 Lustar defence360agent/migrations/__pycache__/193_remove_sent_to_server_from_wordpress_incident.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/193_remove_sent_to_server_from_wordpress_incident.cpython-3110000644000000000000000000000220700000000000031065 0ustar r_jM"dZddlZddZddZdS)zRemove sent_to_server column from wordpress_incident table. The sent_to_server field is no longer needed for WordPress incident tracking. NFc L|jd}||ddS)Nwordpress_incidentsent_to_server)orm remove_fieldsmigratordatabasefakekwargsWordpressIncidents /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.pymigrater s. %9: ,.>?????c v|jd}||tjdddS)NrF)nulldefault)r)r add_fieldspw BooleanFieldrs rrollbackrsL %9: E5AAAr)F)__doc__peeweerrrrrrsS @@@@ r././@LongLink0000644000000000000000000000015100000000000007770 Lustar defence360agent/migrations/__pycache__/193_remove_sent_to_server_from_wordpress_incident.cpython-311.pycdefence360agent/migrations/__pycache__/193_remove_sent_to_server_from_wordpress_incident.cpython-3110000644000000000000000000000220700000000000031065 0ustar r_jM"dZddlZddZddZdS)zRemove sent_to_server column from wordpress_incident table. The sent_to_server field is no longer needed for WordPress incident tracking. NFc L|jd}||ddS)Nwordpress_incidentsent_to_server)orm remove_fieldsmigratordatabasefakekwargsWordpressIncidents /opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.pymigrater s. %9: ,.>?????c v|jd}||tjdddS)NrF)nulldefault)r)r add_fieldspw BooleanFieldrs rrollbackrsL %9: E5AAAr)F)__doc__peeweerrrrrrsS @@@@ rdefence360agent/migrations/__pycache__/194_add_wp_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000000404300000000000025470 0ustar r_j"dZddlZddZddZdS)zAdd wp_disabled_rules table for WordPress-specific disabled rules. This table stores disabled WordPress protection rules with a scope-based design supporting global and domain-level disables. NFc `Gddtj}||dS)NceZdZGddZejZejdZejdZ ejdZ ej dZ ejdZ ejdZdS)migrate..WPDisabledRuleceZdZdZdZdS)$migrate..WPDisabledRule.Metawp_disabled_rules)))rule_idscope scope_valueTN)__name__ __module__ __qualname__db_tableindexesi/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_add_wp_disabled_rules.pyMetar s*HDGGGrrF)nullTN)r r rrpwPrimaryKeyFieldid CharFieldr r r FloatField disabled_atsource IntegerFieldcreated_by_user_idrrrWPDisabledRuler s E E E E E E E E R  ! !",E*** %((("bl--- #bm/// 5))),R_%888rr)rModel create_modelmigratordatabasefakekwargsrs rmigrater' sJ 9 9 9 9 9 9 9 9 .)))))rc J|jd}||dS)Nr)orm remove_modelr"s rrollbackr+s)\"56N .)))))r)F)__doc__peeweerr'r+rrrr.sO ****"******rdefence360agent/migrations/__pycache__/194_add_wp_disabled_rules.cpython-311.pyc0000644000000000000000000000404300000000000024531 0ustar r_j"dZddlZddZddZdS)zAdd wp_disabled_rules table for WordPress-specific disabled rules. This table stores disabled WordPress protection rules with a scope-based design supporting global and domain-level disables. NFc `Gddtj}||dS)NceZdZGddZejZejdZejdZ ejdZ ej dZ ejdZ ejdZdS)migrate..WPDisabledRuleceZdZdZdZdS)$migrate..WPDisabledRule.Metawp_disabled_rules)))rule_idscope scope_valueTN)__name__ __module__ __qualname__db_tableindexesi/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_add_wp_disabled_rules.pyMetar s*HDGGGrrF)nullTN)r r rrpwPrimaryKeyFieldid CharFieldr r r FloatField disabled_atsource IntegerFieldcreated_by_user_idrrrWPDisabledRuler s E E E E E E E E R  ! !",E*** %((("bl--- #bm/// 5))),R_%888rr)rModel create_modelmigratordatabasefakekwargsrs rmigrater' sJ 9 9 9 9 9 9 9 9 .)))))rc J|jd}||dS)Nr)orm remove_modelr"s rrollbackr+s)\"56N .)))))r)F)__doc__peeweerr'r+rrrr.sO ****"******rdefence360agent/migrations/__pycache__/194_create_nonprivileged_config.cpython-311.opt-1.pyc0000644000000000000000000000247600000000000026716 0ustar r_jNdZddlZddlmZejeZddZddZdS)z[ Create imunify360-merged-nonprivileged.config with settings needed by non-root processes. N)MergerFc |rdS tjtddS#t$r&}td|Yd}~dSd}~wwxYw)Nz)Successfully created nonprivileged configz)Failed to create nonprivileged config: %s)rupdate_merged_configloggerinfo Exceptionerror)migratordatabasefakekwargsexcs o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_create_nonprivileged_config.pymigrater s    #%%% ?@@@@@     7           s-5 A%A  A%c dS)N)r r r r s rrollbackrsD)F) __doc__logging defence360agent.contracts.configr getLogger__name__rrrrrrrsp333333  8 $ $    "      rdefence360agent/migrations/__pycache__/194_create_nonprivileged_config.cpython-311.pyc0000644000000000000000000000247600000000000025757 0ustar r_jNdZddlZddlmZejeZddZddZdS)z[ Create imunify360-merged-nonprivileged.config with settings needed by non-root processes. N)MergerFc |rdS tjtddS#t$r&}td|Yd}~dSd}~wwxYw)Nz)Successfully created nonprivileged configz)Failed to create nonprivileged config: %s)rupdate_merged_configloggerinfo Exceptionerror)migratordatabasefakekwargsexcs o/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/194_create_nonprivileged_config.pymigrater s    #%%% ?@@@@@     7           s-5 A%A  A%c dS)N)r r r r s rrollbackrsD)F) __doc__logging defence360agent.contracts.configr getLogger__name__rrrrrrrsp333333  8 $ $    "      rdefence360agent/migrations/__pycache__/195_create_wordpress_site.cpython-311.opt-1.pyc0000644000000000000000000000306600000000000025575 0ustar r_jmJdZddlmZmZmZGddeZd dZd dZdS) zCreate wordpress_site table. migrator.create_model() emits CREATE TABLE IF NOT EXISTS, so this is a no-op on installs where imav/014 (now retained as a no-op) had already created the table. ) IntegerField CharFieldModelcreZdZGddZeddZedZedZdS) WordpressSiteceZdZdZdS)WordpressSite.Metawordpress_siteN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/195_create_wordpress_site.pyMetar s#rrTF) primary_keynull)rN) r r r rrdocrootdomainruidrrrrr sp$$$$$$$$iDu555G YE " " "F ,E " " "CCCrrFc :|tdSN) create_modelrmigratordatabasefakekwargss rmigrater s -(((((rc dSrrrs rrollbackr"sDrN)F)__doc__peeweerrrrr r"rrrr%s2111111111#####E###))))      rdefence360agent/migrations/__pycache__/195_create_wordpress_site.cpython-311.pyc0000644000000000000000000000306600000000000024636 0ustar r_jmJdZddlmZmZmZGddeZd dZd dZdS) zCreate wordpress_site table. migrator.create_model() emits CREATE TABLE IF NOT EXISTS, so this is a no-op on installs where imav/014 (now retained as a no-op) had already created the table. ) IntegerField CharFieldModelcreZdZGddZeddZedZedZdS) WordpressSiteceZdZdZdS)WordpressSite.Metawordpress_siteN)__name__ __module__ __qualname__db_tablei/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/195_create_wordpress_site.pyMetar s#rrTF) primary_keynull)rN) r r r rrdocrootdomainruidrrrrr sp$$$$$$$$iDu555G YE " " "F ,E " " "CCCrrFc :|tdSN) create_modelrmigratordatabasefakekwargss rmigrater s -(((((rc dSrrrs rrollbackr"sDrN)F)__doc__peeweerrrrr r"rrrr%s2111111111#####E###))))      rdefence360agent/migrations/__pycache__/196_add_disabled_rules_sync_ts.cpython-311.opt-1.pyc0000644000000000000000000000214100000000000026523 0ustar r_j7&dZddlmZddZddZdS)zzAdd disabled_rules_sync_ts field to wordpress_site table. Tracks when disabled-rules.php was last written for each site. ) FloatFieldFc l|jd}||tdddS)Nwordpress_siteT)nulldefault)disabled_rules_sync_ts)orm add_fieldsrmigratordatabasefakekwargs WordpressSites n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/196_add_disabled_rules_sync_ts.pymigrater sIL!12M )tTBBBc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs,L!12M =*BCCCCCrN)F)__doc__peeweerrrrrrs[ DDDDDDrdefence360agent/migrations/__pycache__/196_add_disabled_rules_sync_ts.cpython-311.pyc0000644000000000000000000000214100000000000025564 0ustar r_j7&dZddlmZddZddZdS)zzAdd disabled_rules_sync_ts field to wordpress_site table. Tracks when disabled-rules.php was last written for each site. ) FloatFieldFc l|jd}||tdddS)Nwordpress_siteT)nulldefault)disabled_rules_sync_ts)orm add_fieldsrmigratordatabasefakekwargs WordpressSites n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/196_add_disabled_rules_sync_ts.pymigrater sIL!12M )tTBBBc L|jd}||ddS)Nrr)r remove_fieldsr s rrollbackrs,L!12M =*BCCCCCrN)F)__doc__peeweerrrrrrs[ DDDDDDr././@LongLink0000644000000000000000000000015000000000000007767 Lustar defence360agent/migrations/__pycache__/197_add_wordpress_site_manually_deleted_at.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/197_add_wordpress_site_manually_deleted_at.cpython-311.opt-1.0000644000000000000000000000261100000000000030417 0ustar r_j&dZddlmZddZddZdS)zAdd manually_deleted_at column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/015 (now retained as a no-op) had already added the column. )TimestampFieldFc |rdSd|dD}d|vr4|jd}||tddSdS)Ncg|] }|j S)name).0cols z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py zmigrate.. sJJJCsxJJJwordpress_sitemanually_deleted_atT)null)r) get_columnsorm add_columnsr)migratordatabasefakekwargscolumns WordpressSites r migrater s JJ8#7#78H#I#IJJJGG++ %56  ~4/H/H/H      ,+r c dS)Nr)rrrrs r rollbackrsDr N)F)__doc__peeweerrrrr r rsU "!!!!!          r defence360agent/migrations/__pycache__/197_add_wordpress_site_manually_deleted_at.cpython-311.pyc0000644000000000000000000000261100000000000030174 0ustar r_j&dZddlmZddZddZdS)zAdd manually_deleted_at column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/015 (now retained as a no-op) had already added the column. )TimestampFieldFc |rdSd|dD}d|vr4|jd}||tddSdS)Ncg|] }|j S)name).0cols z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py zmigrate.. sJJJCsxJJJwordpress_sitemanually_deleted_atT)null)r) get_columnsorm add_columnsr)migratordatabasefakekwargscolumns WordpressSites r migrater s JJ8#7#78H#I#IJJJGG++ %56  ~4/H/H/H      ,+r c dS)Nr)rrrrs r rollbackrsDr N)F)__doc__peeweerrrrr r rsU "!!!!!          r defence360agent/migrations/__pycache__/198_add_wordpress_site_version.cpython-311.opt-1.pyc0000644000000000000000000000256400000000000026634 0ustar r_j&dZddlmZddZddZdS)zAdd version column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/017 (now retained as a no-op) had already added the column. ) CharFieldFc |rdSd|dD}d|vr5|jd}||tdddSdS)Ncg|] }|j S)name).0cols n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/198_add_wordpress_site_version.py zmigrate.. sJJJCsxJJJwordpress_siteversionz1.0.0F)defaultnull)r) get_columnsorm add_columnsr)migratordatabasefakekwargscolumns WordpressSites r migrater s JJ8#7#78H#I#IJJJG %56  9W5#I#I#I       r c dS)Nr)rrrrs r rollbackrsDr N)F)__doc__peeweerrrrr r rsU           r defence360agent/migrations/__pycache__/198_add_wordpress_site_version.cpython-311.pyc0000644000000000000000000000256400000000000025675 0ustar r_j&dZddlmZddZddZdS)zAdd version column to wordpress_site table. The database.get_columns() guard makes this idempotent on installs where imav/017 (now retained as a no-op) had already added the column. ) CharFieldFc |rdSd|dD}d|vr5|jd}||tdddSdS)Ncg|] }|j S)name).0cols n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/198_add_wordpress_site_version.py zmigrate.. sJJJCsxJJJwordpress_siteversionz1.0.0F)defaultnull)r) get_columnsorm add_columnsr)migratordatabasefakekwargscolumns WordpressSites r migrater s JJ8#7#78H#I#IJJJG %56  9W5#I#I#I       r c dS)Nr)rrrrs r rollbackrsDr N)F)__doc__peeweerrrrr r rsU           r defence360agent/migrations/__pycache__/199_proactive_log_permission.cpython-311.opt-1.pyc0000644000000000000000000000526100000000000026306 0ustar r_jfdZddlmZmZmZmZddlmZmZm Z m Z GddeZ d dZ d dZ d S) aAllow `log` as a proactive feature-management permission value. Relaxes the CHECK constraint on ``feature_management_permissions.proactive`` from ``(NA, FULL)`` to ``(NA, LOG, FULL)``. SQLite cannot alter CHECK constraints in place, so the table is recreated. DEF-42523. ) CharFieldCheckModel TextField) AV_REPORTFULLLOGNAc eZdZGddZedZedede e e ge Z edede e e ge Zd S) FeatureManagementPermsceZdZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__db_tablel/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/199_proactive_log_permission.pyMetars3rrT)uniqueFzproactive in ('{}','{}','{}'))null constraintsdefaultzav in ('{}','{}','{}')N)rrrrruserrrformatr r r proactiveravrrrr r s44444444 9D ! ! !D  E188S$GG H H  I   E*11"iFF G G     BBBrr Fc |rdS|d|t|d|ddS)NzWALTER TABLE feature_management_permissions RENAME TO feature_management_permissions_oldzINSERT INTO feature_management_permissions(user, proactive, av) SELECT user, proactive, av FROM feature_management_permissions_oldz-DROP TABLE feature_management_permissions_old)sql create_modelr migratordatabasefakekwargss rmigrater()sx  LL 7 0111 LL M LL@AAAAArc dS)Nrr#s rrollbackr*8sDrN)F)__doc__peeweerrrr,defence360agent.feature_management.constantsrrr r r r(r*rrrr.s655555555555U* B B B B      rdefence360agent/migrations/__pycache__/199_proactive_log_permission.cpython-311.pyc0000644000000000000000000000526100000000000025347 0ustar r_jfdZddlmZmZmZmZddlmZmZm Z m Z GddeZ d dZ d dZ d S) aAllow `log` as a proactive feature-management permission value. Relaxes the CHECK constraint on ``feature_management_permissions.proactive`` from ``(NA, FULL)`` to ``(NA, LOG, FULL)``. SQLite cannot alter CHECK constraints in place, so the table is recreated. DEF-42523. ) CharFieldCheckModel TextField) AV_REPORTFULLLOGNAc eZdZGddZedZedede e e ge Z edede e e ge Zd S) FeatureManagementPermsceZdZdZdS)FeatureManagementPerms.Metafeature_management_permissionsN)__name__ __module__ __qualname__db_tablel/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/199_proactive_log_permission.pyMetars3rrT)uniqueFzproactive in ('{}','{}','{}'))null constraintsdefaultzav in ('{}','{}','{}')N)rrrrruserrrformatr r r proactiveravrrrr r s44444444 9D ! ! !D  E188S$GG H H  I   E*11"iFF G G     BBBrr Fc |rdS|d|t|d|ddS)NzWALTER TABLE feature_management_permissions RENAME TO feature_management_permissions_oldzINSERT INTO feature_management_permissions(user, proactive, av) SELECT user, proactive, av FROM feature_management_permissions_oldz-DROP TABLE feature_management_permissions_old)sql create_modelr migratordatabasefakekwargss rmigrater()sx  LL 7 0111 LL M LL@AAAAArc dS)Nrr#s rrollbackr*8sDrN)F)__doc__peeweerrrr,defence360agent.feature_management.constantsrrr r r r(r*rrrr.s655555555555U* B B B B      rdefence360agent/migrations/__pycache__/200_seed_per_user_waf_enabled.cpython-311.opt-1.pyc0000644000000000000000000000623700000000000026317 0ustar r_jgddlZddlZddlmZmZmZddlmZejdddZ ej e Z d dZ d d ZdS) N) UserConfigUserTypechoose_value_from_config)importerzimav.malwarelib.utils.user_list panel_users)modulenamedefaultFc |stdStj}tj| |t}n?#t $r2t dY|dSwxYw|D]} |d}n:#ttf$r&}t d||Yd}~>d}~wwxYw tdd|\} } | tjkrmn3#t $r&}t d||Yd}~d}~wwxYw t|dddiid #t $r&}t d ||Yd}~d}~wwxYw |dS#|wxYw) Nz4Failed to enumerate panel users for waf_enabled seeduserz=Skipping malformed panel entry %r during waf_enabled seed: %s WORDPRESS waf_enabled)usernamez8Failed to read waf_enabled for user %s while seeding: %sT)without_defaultsz4Failed to seed WORDPRESS.waf_enabled for user %s: %s)rasyncionew_event_loopset_event_looprun_until_complete Exceptionlogger exceptioncloseKeyError TypeErrorwarningrrROOTrdict_to_config) migratordatabasefakekwargsloopusersentryre_sources m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/200_seed_per_user_waf_enabled.pymigrater)sg {"  ! # #D 4   / ++KMM::EE      F    P Y  $ $ E  =i(        4!% 6 X]**+   N    H---<< =$"78%)=   J ?$ L  s!AF.$B;F.BF.B$#F.$C5CF.CF.%DF. D6D1,F.1D66F.:)E$#F.$ F.F F.FF..Gc dS)N)rrr r!s r(rollbackr,LsD)F)rlogging defence360agent.contracts.configrrrdefence360agent.utilsrgetr getLogger__name__rr)r,r+r-r(r4s +*****hl ,    8 $ $5555p      r-defence360agent/migrations/__pycache__/200_seed_per_user_waf_enabled.cpython-311.pyc0000644000000000000000000000623700000000000025360 0ustar r_jgddlZddlZddlmZmZmZddlmZejdddZ ej e Z d dZ d d ZdS) N) UserConfigUserTypechoose_value_from_config)importerzimav.malwarelib.utils.user_list panel_users)modulenamedefaultFc |stdStj}tj| |t}n?#t $r2t dY|dSwxYw|D]} |d}n:#ttf$r&}t d||Yd}~>d}~wwxYw tdd|\} } | tjkrmn3#t $r&}t d||Yd}~d}~wwxYw t|dddiid #t $r&}t d ||Yd}~d}~wwxYw |dS#|wxYw) Nz4Failed to enumerate panel users for waf_enabled seeduserz=Skipping malformed panel entry %r during waf_enabled seed: %s WORDPRESS waf_enabled)usernamez8Failed to read waf_enabled for user %s while seeding: %sT)without_defaultsz4Failed to seed WORDPRESS.waf_enabled for user %s: %s)rasyncionew_event_loopset_event_looprun_until_complete Exceptionlogger exceptioncloseKeyError TypeErrorwarningrrROOTrdict_to_config) migratordatabasefakekwargsloopusersentryre_sources m/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/200_seed_per_user_waf_enabled.pymigrater)sg {"  ! # #D 4   / ++KMM::EE      F    P Y  $ $ E  =i(        4!% 6 X]**+   N    H---<< =$"78%)=   J ?$ L  s!AF.$B;F.BF.B$#F.$C5CF.CF.%DF. D6D1,F.1D66F.:)E$#F.$ F.F F.FF..Gc dS)N)rrr r!s r(rollbackr,LsD)F)rlogging defence360agent.contracts.configrrrdefence360agent.utilsrgetr getLogger__name__rr)r,r+r-r(r4s +*****hl ,    8 $ $5555p      r-defence360agent/migrations/__pycache__/201_rerender_nonprivileged_config.cpython-311.opt-1.pyc0000644000000000000000000000235500000000000027242 0ustar r_jBNdZddlZddlmZejeZddZddZdS)z Re-render imunify360-merged-nonprivileged.config so the newly-split MALWARE_SCANNING.enable_scan_modsec key lands on upgrade. N)MergerFc |rdS tjdS#t$r&}td|Yd}~dSd}~wwxYw)Nz,Failed to re-render nonprivileged config: %s)rupdate_merged_config Exceptionloggererror)migratordatabasefakekwargsexcs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/201_rerender_nonprivileged_config.pymigrater s  #%%%%%     :           s A AA c dS)N)r r r r s rrollbackrsD)F) __doc__logging defence360agent.contracts.configr getLogger__name__rrrrrrrsp333333  8 $ $           rdefence360agent/migrations/__pycache__/201_rerender_nonprivileged_config.cpython-311.pyc0000644000000000000000000000235500000000000026303 0ustar r_jBNdZddlZddlmZejeZddZddZdS)z Re-render imunify360-merged-nonprivileged.config so the newly-split MALWARE_SCANNING.enable_scan_modsec key lands on upgrade. N)MergerFc |rdS tjdS#t$r&}td|Yd}~dSd}~wwxYw)Nz,Failed to re-render nonprivileged config: %s)rupdate_merged_config Exceptionloggererror)migratordatabasefakekwargsexcs q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/201_rerender_nonprivileged_config.pymigrater s  #%%%%%     :           s A AA c dS)N)r r r r s rrollbackrsD)F) __doc__logging defence360agent.contracts.configr getLogger__name__rrrrrrrsp333333  8 $ $           rdefence360agent/migrations/__pycache__/202_add_wordpress_incident_bucket.cpython-311.opt-1.pyc0000644000000000000000000000564100000000000027236 0ustar r_jZ0dZddlZdZedzZddZddZdS) zGGG$GGG LLCDDDDDc |jd}dt}ddtD}|d|j|gt R|d|d|d|d |d||d |j|gtRd d idS) Nr z, z AND c3&K|] }d|d|V dS)zdup.z IS wordpress_incident.N).0columns r zrollback..sF  7v66f66rzXCREATE INDEX IF NOT EXISTS wordpressincident_timestamp ON wordpress_incident (timestamp)zcUPDATE wordpress_incident SET retries = (SELECT SUM(dup.retries) FROM wordpress_incident dup WHERE zL), timestamp = (SELECT MIN(dup.timestamp) FROM wordpress_incident dup WHERE )z`DELETE FROM wordpress_incident WHERE id NOT IN (SELECT MIN(id) FROM wordpress_incident GROUP BY r rT)rjoinrrrr remove_fieldsr)rrrrrkeysame_keys rrollbackr+s? %9: ))N # #C||$H  LL -H);N;;;; LL        LL D=@ D D D ,h777H(G>GGG$GGGGGr)F)__doc__peeweerrrrr+r"rrr.sbBBK+- E E E E HHHHHHrdefence360agent/migrations/__pycache__/202_add_wordpress_incident_bucket.cpython-311.pyc0000644000000000000000000000564100000000000026277 0ustar r_jZ0dZddlZdZedzZddZddZdS) zGGG$GGG LLCDDDDDc |jd}dt}ddtD}|d|j|gt R|d|d|d|d |d||d |j|gtRd d idS) Nr z, z AND c3&K|] }d|d|V dS)zdup.z IS wordpress_incident.N).0columns r zrollback..sF  7v66f66rzXCREATE INDEX IF NOT EXISTS wordpressincident_timestamp ON wordpress_incident (timestamp)zcUPDATE wordpress_incident SET retries = (SELECT SUM(dup.retries) FROM wordpress_incident dup WHERE zL), timestamp = (SELECT MIN(dup.timestamp) FROM wordpress_incident dup WHERE )z`DELETE FROM wordpress_incident WHERE id NOT IN (SELECT MIN(id) FROM wordpress_incident GROUP BY r rT)rjoinrrrr remove_fieldsr)rrrrrkeysame_keys rrollbackr+s? %9: ))N # #C||$H  LL -H);N;;;; LL        LL D=@ D D D ,h777H(G>GGG$GGGGGr)F)__doc__peeweerrrrr+r"rrr.sbBBK+- E E E E HHHHHHr././@LongLink0000644000000000000000000000014700000000000007775 Lustar defence360agent/migrations/__pycache__/203_add_wordpress_incident_unsent_retries.cpython-311.opt-1.pycdefence360agent/migrations/__pycache__/203_add_wordpress_incident_unsent_retries.cpython-311.opt-1.p0000644000000000000000000000331100000000000030467 0ustar r_jL"dZddlZddZddZdS)a_Track how many occurrences of each wordpress_incident correlation owes. The counter is decremented only once the transport acknowledges the message that carried them, so the periodic task can re-send incidents whose message was lost. A counter rather than a flag: occurrences merged into a row that was already reported still have to reach correlation. Rows that already exist when the column is added take the DEFAULT of 0 and are therefore treated as fully reported. Their delivery was never tracked, and re-sending a whole retention window of history on upgrade would be worse than leaving them alone. NFc |jd}||tjdddtjdgdS)Nwordpress_incidentFrTz DEFAULT 0)nulldefaultindex constraints)unsent_retries)orm add_fieldspw IntegerFieldSQLmigratordatabasefakekwargsWordpressIncidents y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/203_add_wordpress_incident_unsent_retries.pymigratersi %9:  ,,-         c L|jd}||ddS)Nrr )r remove_fieldsrs rrollbackr!s. %9: ,.>?????r)F)__doc__peeweer rrrrrsU       @@@@@@rdefence360agent/migrations/__pycache__/203_add_wordpress_incident_unsent_retries.cpython-311.pyc0000644000000000000000000000331100000000000030064 0ustar r_jL"dZddlZddZddZdS)a_Track how many occurrences of each wordpress_incident correlation owes. The counter is decremented only once the transport acknowledges the message that carried them, so the periodic task can re-send incidents whose message was lost. A counter rather than a flag: occurrences merged into a row that was already reported still have to reach correlation. Rows that already exist when the column is added take the DEFAULT of 0 and are therefore treated as fully reported. Their delivery was never tracked, and re-sending a whole retention window of history on upgrade would be worse than leaving them alone. NFc |jd}||tjdddtjdgdS)Nwordpress_incidentFrTz DEFAULT 0)nulldefaultindex constraints)unsent_retries)orm add_fieldspw IntegerFieldSQLmigratordatabasefakekwargsWordpressIncidents y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/203_add_wordpress_incident_unsent_retries.pymigratersi %9:  ,,-         c L|jd}||ddS)Nrr )r remove_fieldsrs rrollbackr!s. %9: ,.>?????r)F)__doc__peeweer rrrrrsU       @@@@@@rdefence360agent/migrations/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030500000000000022330 0ustar r_jdS)NrX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/__init__.pyrsrdefence360agent/migrations/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030500000000000021371 0ustar r_jdS)NrX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/__init__.pyrsrdefence360agent/migrations/__pycache__/conf.cpython-311.opt-1.pyc0000644000000000000000000000057200000000000021524 0ustar r_jtFddlmZdejZdS))Modelz sqlite:///{}N) defence360agent.contracts.configrformatPATHDATABASET/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/conf.pyr s0222222   , ,r defence360agent/migrations/__pycache__/conf.cpython-311.pyc0000644000000000000000000000057200000000000020565 0ustar r_jtFddlmZdejZdS))Modelz sqlite:///{}N) defence360agent.contracts.configrformatPATHDATABASET/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/migrations/conf.pyr s0222222   , ,r defence360agent/migrations/conf.py0000644000000000000000000000016400000000000014222 0ustar # Migration config from defence360agent.contracts.config import Model DATABASE = "sqlite:///{}".format(Model.PATH) defence360agent/model/0000755000000000000000000000000000000000000011646 5ustar defence360agent/model/__init__.py0000644000000000000000000000170300000000000013760 0ustar from peewee import IntegrityError, Model as BaseModel class Model(BaseModel): """ Common Model class that fix create_or_get method with using CompositeKey. https://stackoverflow.com/questions/35167628/peewee-create-or-get-error-in-model-with-compositekey """ @classmethod def create_or_get(cls, **kwargs): try: with cls._meta.database.atomic(): return cls.create(**kwargs), True except IntegrityError: query = [] for field_name, value in kwargs.items(): field = getattr(cls, field_name) field_is_primary_key = ( field.name in cls._meta.primary_key.field_names if cls._meta.composite_key else field.primary_key ) if field.unique or field_is_primary_key: query.append(field == value) return cls.get(*query), False defence360agent/model/__pycache__/0000755000000000000000000000000000000000000014056 5ustar defence360agent/model/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000360200000000000021257 0ustar r_j2ddlmZmZGddeZdS))IntegrityErrorModelc(eZdZdZedZdS)rz Common Model class that fix create_or_get method with using CompositeKey. https://stackoverflow.com/questions/35167628/peewee-create-or-get-error-in-model-with-compositekey c  |jj5|jdi|dfcdddS#1swxYwYdS#t$rg}|D]b\}}t ||}|jjr|j|jj j vn|j }|j s|r| ||kc|j |dfcYSwxYw)NTF)_metadatabaseatomiccreateritemsgetattr composite_keyname primary_key field_namesuniqueappendget)clskwargsquery field_namevaluefieldfield_is_primary_keys S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/__init__.py create_or_getzModel.create_or_get sY *#**,, 2 2!sz++F++T1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 * * *E%+\\^^ 1 1! EZ00y.+EJ#)"7"CCC*% <1#71LL%00037E?E) ) ) ) *s2A < A AA AA BCCN)__name__ __module__ __qualname____doc__ classmethodrrrrrs9 **[***r#rN)peeweerr BaseModelrr#rr&sQ55555555*****I*****r#defence360agent/model/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000360200000000000020320 0ustar r_j2ddlmZmZGddeZdS))IntegrityErrorModelc(eZdZdZedZdS)rz Common Model class that fix create_or_get method with using CompositeKey. https://stackoverflow.com/questions/35167628/peewee-create-or-get-error-in-model-with-compositekey c  |jj5|jdi|dfcdddS#1swxYwYdS#t$rg}|D]b\}}t ||}|jjr|j|jj j vn|j }|j s|r| ||kc|j |dfcYSwxYw)NTF)_metadatabaseatomiccreateritemsgetattr composite_keyname primary_key field_namesuniqueappendget)clskwargsquery field_namevaluefieldfield_is_primary_keys S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/__init__.py create_or_getzModel.create_or_get sY *#**,, 2 2!sz++F++T1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 * * *E%+\\^^ 1 1! EZ00y.+EJ#)"7"CCC*% <1#71LL%00037E?E) ) ) ) *s2A < A AA AA BCCN)__name__ __module__ __qualname____doc__ classmethodrrrrrs9 **[***r#rN)peeweerr BaseModelrr#rr&sQ55555555*****I*****r#defence360agent/model/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000001332100000000000022701 0ustar r_j NddlZddlmZmZddlmZmZmZGddeZdS)N)Modelinstance)datetimetimezone timedeltaceZdZdZGddZejZejdZ ejdZ ej dZ ej dejejZejddejdg Zej dejejZed Zedd ZeddZededzfdZedZedZdS)AnalystCleanupRequestz Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. c eZdZejZdZdS)AnalystCleanupRequest.Metaanalyst_cleanup_requestsN)__name__ __module__ __qualname__rdbdatabasedb_tableZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/analyst_cleanup.pyMetar s;-rrF)null)rdefaultpendingz/status in ('pending','in_progress','completed'))rr constraintsc2||||S)zCreate a new cleanup request)username zendesk_id ticket_link)create)clsrrrs rcreate_requestz$AnalystCleanupRequest.create_request"s&zz*+   r2rc||j|k|j||S)z$Get all requests for a specific user)selectwhererorder_by created_atdesclimitoffset)r rr)r*s rget_user_requestsz'AnalystCleanupRequest.get_user_requests)sY JJLL U3<8+ , , Xcn))++ , , U5\\ VF^^  rc||j||S)zGet all requests for a sever)r$r&r'r(r)r*)r r)r*s rget_all_requestsz&AnalystCleanupRequest.get_all_requests4sE JJLL Xcn))++ , , U5\\ VF^^  rreturnNc||j|k|jddgzd}|r|jndS)z Gets user requests for a user and checks if there are requests with [pending | in_progress] state. If found, returns ticket_link, otherwise returns None r in_progressN)r$r%rstatusin_r)firstr)r ractive_requests rget_active_request_linkz-AnalystCleanupRequest.get_active_request_link>ss JJLL U):>>9m"<==?U1XX %'' .<E~))Erc||||j|kS)zUpdate the status of a request)r2 last_updated)updater%rexecute)r r new_statusr8s r update_statusz#AnalystCleanupRequest.update_statusPs9 JJj|J D D U3>Z/ 0 0 WYY rctjtjt dz }t t jt jt j t j  t j ddgt j dkt j |kzzS)z Returns a query to fetch active cleanup requests and recently completed requests for the specified users. )daysrr0 completed) rnowrutcrr r$rrr2r8r%r3)r three_days_agos rget_all_relevant_requestsz/AnalystCleanupRequest.get_all_relevant_requestsYs"hl33iQ6G6G6GG$++ ! * ! , ! ( ! .   % " ) - -y-.H I I&-<(5GI   r)r"r)r rr__doc__rpw AutoFieldid CharFieldrr TextFieldrTimestampFieldrrArrBr'Checkr2r8 classmethodr!r+r-strr6r<rDrrrr r s ........ Br|'''H5)))J",E***K"" LHL66JR\  BHF G G F%2$ LHL66L  [    [    [ F#*FFF[F"  [   [   rr ) peeweerFdefence360agent.modelrrrrrr rrrrQs111111112222222222f f f f f Ef f f f f rdefence360agent/model/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000001332100000000000021742 0ustar r_j NddlZddlmZmZddlmZmZmZGddeZdS)N)Modelinstance)datetimetimezone timedeltaceZdZdZGddZejZejdZ ejdZ ej dZ ej dejejZejddejdg Zej dejejZed Zedd ZeddZededzfdZedZedZdS)AnalystCleanupRequestz Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. c eZdZejZdZdS)AnalystCleanupRequest.Metaanalyst_cleanup_requestsN)__name__ __module__ __qualname__rdbdatabasedb_tableZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/analyst_cleanup.pyMetar s;-rrF)null)rdefaultpendingz/status in ('pending','in_progress','completed'))rr constraintsc2||||S)zCreate a new cleanup request)username zendesk_id ticket_link)create)clsrrrs rcreate_requestz$AnalystCleanupRequest.create_request"s&zz*+   r2rc||j|k|j||S)z$Get all requests for a specific user)selectwhererorder_by created_atdesclimitoffset)r rr)r*s rget_user_requestsz'AnalystCleanupRequest.get_user_requests)sY JJLL U3<8+ , , Xcn))++ , , U5\\ VF^^  rc||j||S)zGet all requests for a sever)r$r&r'r(r)r*)r r)r*s rget_all_requestsz&AnalystCleanupRequest.get_all_requests4sE JJLL Xcn))++ , , U5\\ VF^^  rreturnNc||j|k|jddgzd}|r|jndS)z Gets user requests for a user and checks if there are requests with [pending | in_progress] state. If found, returns ticket_link, otherwise returns None r in_progressN)r$r%rstatusin_r)firstr)r ractive_requests rget_active_request_linkz-AnalystCleanupRequest.get_active_request_link>ss JJLL U):>>9m"<==?U1XX %'' .<E~))Erc||||j|kS)zUpdate the status of a request)r2 last_updated)updater%rexecute)r r new_statusr8s r update_statusz#AnalystCleanupRequest.update_statusPs9 JJj|J D D U3>Z/ 0 0 WYY rctjtjt dz }t t jt jt j t j  t j ddgt j dkt j |kzzS)z Returns a query to fetch active cleanup requests and recently completed requests for the specified users. )daysrr0 completed) rnowrutcrr r$rrr2r8r%r3)r three_days_agos rget_all_relevant_requestsz/AnalystCleanupRequest.get_all_relevant_requestsYs"hl33iQ6G6G6GG$++ ! * ! , ! ( ! .   % " ) - -y-.H I I&-<(5GI   r)r"r)r rr__doc__rpw AutoFieldid CharFieldrr TextFieldrTimestampFieldrrArrBr'Checkr2r8 classmethodr!r+r-strr6r<rDrrrr r s ........ Br|'''H5)))J",E***K"" LHL66JR\  BHF G G F%2$ LHL66L  [    [    [ F#*FFF[F"  [   [   rr ) peeweerFdefence360agent.modelrrrrrr rrrrQs111111112222222222f f f f f Ef f f f f rdefence360agent/model/__pycache__/event_hook.cpython-311.opt-1.pyc0000644000000000000000000000676300000000000021674 0ustar r_j^ddlmZddlmZmZmZddlmZmZddlm Z GddeZ dS))time) CharField IntegerField BooleanField)instanceModel) FilenameFieldceZdZdZGddZedZedZe ddZ e dZ e d Ze dd Ze d Zd Zd S) EventHookzwImunify Hooks v1.0 configuration. .. deprecated:: 4.10 A new notification system was implemented in DEF-11680 c eZdZejZdZdS)EventHook.Meta event_hookN)__name__ __module__ __qualname__rdbdatabasedb_tableU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/event_hook.pyMetar s;rrF)nullc8ttSN)intrrrrzEventHook.ss466{{r)rdefault)rc|}|dkr||j|k}t|S)Nall)selectwhereeventlistdicts)clsr#qs r list_eventszEventHook.list_eventssD JJLL E>> U*++AAGGIIrc||j|k|j|kz}|rdS||||}|S)N)r#pathnative)r!r"r#r*existscreateas_dict)r&r#r*r+r'hooks radd_hookzEventHook.add_hook$sj JJLL   U 2sx47GH I I 88:: 4zzDz@@||~~rc"||j|k|j|kz}|sdS|}|}||Sr)r!r"r#r*r,getr.delete_instance)r&r#r*r'r/datas r delete_hookzEventHook.delete_hook,sv JJLL   U 2sx47GH I Ixxzz 4uuww||~~  rc8|j|j|j|jdS)Nr*r#createdr+r7)selfs rr.zEventHook.as_dict6s%IZ|k    rN)F)rrr__doc__rr r*rr#rr8rr+ classmethodr(r0r5r.rrrr r s         =e $ $ $D I5 ! ! !El/B/BCCCG\% ( ( (F[ [[     rr N) rpeeweerrrdefence360agent.modelrr$defence360agent.model.simplificationr r rrrr?s888888888811111111>>>>>>3 3 3 3 3 3 3 3 3 3 rdefence360agent/model/__pycache__/event_hook.cpython-311.pyc0000644000000000000000000000676300000000000020735 0ustar r_j^ddlmZddlmZmZmZddlmZmZddlm Z GddeZ dS))time) CharField IntegerField BooleanField)instanceModel) FilenameFieldceZdZdZGddZedZedZe ddZ e dZ e d Ze dd Ze d Zd Zd S) EventHookzwImunify Hooks v1.0 configuration. .. deprecated:: 4.10 A new notification system was implemented in DEF-11680 c eZdZejZdZdS)EventHook.Meta event_hookN)__name__ __module__ __qualname__rdbdatabasedb_tableU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/event_hook.pyMetar s;rrF)nullc8ttSN)intrrrrzEventHook.ss466{{r)rdefault)rc|}|dkr||j|k}t|S)Nall)selectwhereeventlistdicts)clsr#qs r list_eventszEventHook.list_eventssD JJLL E>> U*++AAGGIIrc||j|k|j|kz}|rdS||||}|S)N)r#pathnative)r!r"r#r*existscreateas_dict)r&r#r*r+r'hooks radd_hookzEventHook.add_hook$sj JJLL   U 2sx47GH I I 88:: 4zzDz@@||~~rc"||j|k|j|kz}|sdS|}|}||Sr)r!r"r#r*r,getr.delete_instance)r&r#r*r'r/datas r delete_hookzEventHook.delete_hook,sv JJLL   U 2sx47GH I Ixxzz 4uuww||~~  rc8|j|j|j|jdS)Nr*r#createdr+r7)selfs rr.zEventHook.as_dict6s%IZ|k    rN)F)rrr__doc__rr r*rr#rr8rr+ classmethodr(r0r5r.rrrr r s         =e $ $ $D I5 ! ! !El/B/BCCCG\% ( ( (F[ [[     rr N) rpeeweerrrdefence360agent.modelrr$defence360agent.model.simplificationr r rrrr?s888888888811111111>>>>>>3 3 3 3 3 3 3 3 3 3 rdefence360agent/model/__pycache__/icontact.cpython-311.opt-1.pyc0000644000000000000000000000472500000000000021333 0ustar r_jddlZddlmZmZmZddlmZddlmZm Z ddl m Z m Z ej e eje iZGddeZdS)N) CharField IntegerField CompositeKey)IContactMessageType)Modelinstance)DAYWEEKceZdZGddZeZedZedZe d dZ e d d Z dS) IContactThrottlec8eZdZejZdZeddZdS)IContactThrottle.Metaicontact_throttle message_typeuserN) __name__ __module__ __qualname__rdbdatabasedb_tabler primary_keyS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/icontact.pyMetars+;&"l>6:: rrT)nullr)defaultNct|||\}}tj|jz |kS)N)rr) get_or_createtime timestamp)clsr period_limitrobj_s rmay_be_notifiedz IContactThrottle.may_be_notifieds6"" 4"HHQ cm+|;;rc|tj|j|k||jdn |j|kdS)N)r"T)updater!whererris_nullexecute)r#rrs rrefreshzIContactThrottle.refreshsh TY[[ ))//   ,&*lCH  T " " "D8H   ')))))r)N) rrrrrrrrr" classmethodr'r-rrrr r s;;;;;;;; 9;;L 9$   D Q'''I<<<[<[rr )r!peeweerrr defence360agent.contracts.configrdefence360agent.modelrrdefence360agent.utils.commonr r MALWARE_FOUNDSCAN_NOT_SCHEDULEDTHROTTLING_PERIODr rrrr6s 8888888888@@@@@@1111111122222222%s*D urdefence360agent/model/__pycache__/icontact.cpython-311.pyc0000644000000000000000000000472500000000000020374 0ustar r_jddlZddlmZmZmZddlmZddlmZm Z ddl m Z m Z ej e eje iZGddeZdS)N) CharField IntegerField CompositeKey)IContactMessageType)Modelinstance)DAYWEEKceZdZGddZeZedZedZe d dZ e d d Z dS) IContactThrottlec8eZdZejZdZeddZdS)IContactThrottle.Metaicontact_throttle message_typeuserN) __name__ __module__ __qualname__rdbdatabasedb_tabler primary_keyS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/icontact.pyMetars+;&"l>6:: rrT)nullr)defaultNct|||\}}tj|jz |kS)N)rr) get_or_createtime timestamp)clsr period_limitrobj_s rmay_be_notifiedz IContactThrottle.may_be_notifieds6"" 4"HHQ cm+|;;rc|tj|j|k||jdn |j|kdS)N)r"T)updater!whererris_nullexecute)r#rrs rrefreshzIContactThrottle.refreshsh TY[[ ))//   ,&*lCH  T " " "D8H   ')))))r)N) rrrrrrrrr" classmethodr'r-rrrr r s;;;;;;;; 9;;L 9$   D Q'''I<<<[<[rr )r!peeweerrr defence360agent.contracts.configrdefence360agent.modelrrdefence360agent.utils.commonr r MALWARE_FOUNDSCAN_NOT_SCHEDULEDTHROTTLING_PERIODr rrrr6s 8888888888@@@@@@1111111122222222%s*D urdefence360agent/model/__pycache__/infected_domain.cpython-311.opt-1.pyc0000644000000000000000000001325700000000000022637 0ustar r_jddlZddlZddlZddlmZmZmZmZddlm Z m Z ej e Z Gdde ZdS)N) CharField FloatField IntegerField TextField)instanceModelceZdZdZedZedZedZedZ e Z e dZ GddZed d Zed Zd S)InfectedDomainListzDDomains with bad reputation, used for Reputation Management feature.T) primary_key)nullFc eZdZejZdZdS)InfectedDomainList.Metainfected_domain_listN)__name__ __module__ __qualname__rdbdatabasedb_tableZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/infected_domain.pyMetar!s;)rrr2c||j|j|j}fd|D}tj|d}d}g}t|D]P\} } |dz }t||kr3| |kr-| \} } | \} }| | |d| DdQ||fS)Nc30K|]}|dv |VdS)usernameNr).0rowexisting_userss r z1InfectedDomainList.get_by_user..,s:  C O~,M,MC,M,M,M,M  rc"|d|dfS)Nrnamer)rs rz0InfectedDomainList.get_by_user..0ss:F .Lr)keyrc>g|]}|d|d|ddS) threat_typevendor timestamp)typer)r*r)rts r z2InfectedDomainList.get_by_user..>sG$$$ !" )*-(8*+H+-.{^$$$r)rdomainthreats) selectorder_byrr#r*descdicts itertoolsgroupby enumeratelenappend)clsr offsetlimitqueryfiltered_by_usergrouped max_countresultivaluegroupr/rr#s ` r get_by_userzInfectedDomainList.get_by_user%s; %% L#(CM$6$6$8$8       ;;==   # "L"L    !'**  HAu NIF e##!v++!&w!&$ $,"&$$ &- $$$     y  rc d|D}tj5|tj}|D]}|d}||vrt d|*||D]h}|d}|dvr|dd} n|dkr |d} n |d vrd } nd } | || |f|} | ||| || i d d d d S#1swxYwYd S)a Update domain reputatuion info. If threat info already exists, do not update timestamp :param domains: reputation data from server :param domains_to_users: domain -> users mapping from hosting panel :return: cJi|] }|d|d|df|d!S)r#r(r)r*r)rrs r z6InfectedDomainList.refresh_domains..TsB   vY-(!H+ 6+   rr<zUsers for domain %s not found.r))zgoogle-safe-browsingzyandex-safe-browsingdetailsr(spamhaus) phishtank openphishz spam domainTHREAT_TYPE_UNSPECIFIED)rr#r(r)r*N) r0r3rratomicdeleteexecutetimeloggerwarninggetcreate) r9domainsdomains_to_usersexistingnow domain_infor.userr)r(r*s rrefresh_domainsz"InfectedDomainList.refresh_domainsJs  ZZ\\''))   [   ! !   JJLL " " ")++C&   $W-!111NN#CVLLL,V4D(2F"'2)&<]&K :--&1)&< #===&3 &? ( f5s!!IJJ!%#$/%"+ #                    sCD55D9<D9N)rr)rrr__doc__ridrrr#r(rr*rr)r classmethodrDr\rrrr r sNN $ ' ' 'Byd###H 9% D)'''K I YD ! ! !F********"!"!"!["!H,,[,,,rr )r4loggingrQpeeweerrrrdefence360agent.modelrr getLoggerrrRr rrrrds 21111111  8 $ $ffffffffffrdefence360agent/model/__pycache__/infected_domain.cpython-311.pyc0000644000000000000000000001325700000000000021700 0ustar r_jddlZddlZddlZddlmZmZmZmZddlm Z m Z ej e Z Gdde ZdS)N) CharField FloatField IntegerField TextField)instanceModelceZdZdZedZedZedZedZ e Z e dZ GddZed d Zed Zd S)InfectedDomainListzDDomains with bad reputation, used for Reputation Management feature.T) primary_key)nullFc eZdZejZdZdS)InfectedDomainList.Metainfected_domain_listN)__name__ __module__ __qualname__rdbdatabasedb_tableZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/infected_domain.pyMetar!s;)rrr2c||j|j|j}fd|D}tj|d}d}g}t|D]P\} } |dz }t||kr3| |kr-| \} } | \} }| | |d| DdQ||fS)Nc30K|]}|dv |VdS)usernameNr).0rowexisting_userss r z1InfectedDomainList.get_by_user..,s:  C O~,M,MC,M,M,M,M  rc"|d|dfS)Nrnamer)rs rz0InfectedDomainList.get_by_user..0ss:F .Lr)keyrc>g|]}|d|d|ddS) threat_typevendor timestamp)typer)r*r)rts r z2InfectedDomainList.get_by_user..>sG$$$ !" )*-(8*+H+-.{^$$$r)rdomainthreats) selectorder_byrr#r*descdicts itertoolsgroupby enumeratelenappend)clsr offsetlimitqueryfiltered_by_usergrouped max_countresultivaluegroupr/rr#s ` r get_by_userzInfectedDomainList.get_by_user%s; %% L#(CM$6$6$8$8       ;;==   # "L"L    !'**  HAu NIF e##!v++!&w!&$ $,"&$$ &- $$$     y  rc d|D}tj5|tj}|D]}|d}||vrt d|*||D]h}|d}|dvr|dd} n|dkr |d} n |d vrd } nd } | || |f|} | ||| || i d d d d S#1swxYwYd S)a Update domain reputatuion info. If threat info already exists, do not update timestamp :param domains: reputation data from server :param domains_to_users: domain -> users mapping from hosting panel :return: cJi|] }|d|d|df|d!S)r#r(r)r*r)rrs r z6InfectedDomainList.refresh_domains..TsB   vY-(!H+ 6+   rr<zUsers for domain %s not found.r))zgoogle-safe-browsingzyandex-safe-browsingdetailsr(spamhaus) phishtank openphishz spam domainTHREAT_TYPE_UNSPECIFIED)rr#r(r)r*N) r0r3rratomicdeleteexecutetimeloggerwarninggetcreate) r9domainsdomains_to_usersexistingnow domain_infor.userr)r(r*s rrefresh_domainsz"InfectedDomainList.refresh_domainsJs  ZZ\\''))   [   ! !   JJLL " " ")++C&   $W-!111NN#CVLLL,V4D(2F"'2)&<]&K :--&1)&< #===&3 &? ( f5s!!IJJ!%#$/%"+ #                    sCD55D9<D9N)rr)rrr__doc__ridrrr#r(rr*rr)r classmethodrDr\rrrr r sNN $ ' ' 'Byd###H 9% D)'''K I YD ! ! !F********"!"!"!["!H,,[,,,rr )r4loggingrQpeeweerrrrdefence360agent.modelrr getLoggerrrRr rrrrds 21111111  8 $ $ffffffffffrdefence360agent/model/__pycache__/instance.cpython-311.opt-1.pyc0000644000000000000000000000076000000000000021326 0ustar r_jBddlmcmZejdgddZdS)N)) journal_modewal) foreign_keysON) busy_timeouti'T)pragmasregexp_function)defence360agent.model.tls_checkmodel tls_checkSqliteDatabaseWrapperdbS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/instance.pyrsV333333333%Y$    rdefence360agent/model/__pycache__/instance.cpython-311.pyc0000644000000000000000000000076000000000000020367 0ustar r_jBddlmcmZejdgddZdS)N)) journal_modewal) foreign_keysON) busy_timeouti'T)pragmasregexp_function)defence360agent.model.tls_checkmodel tls_checkSqliteDatabaseWrapperdbS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/instance.pyrsV333333333%Y$    rdefence360agent/model/__pycache__/messages_to_send.cpython-311.opt-1.pyc0000644000000000000000000001002700000000000023041 0ustar r_jCNddlmZddlmZmZddlmZmZGddeZdS)) namedtuple) FloatField BlobField)instanceModelceZdZdZGddZedZedZe ddZ e dZ e d Z e d Ze dd Ze dfd ZxZS) MessageToSendzc Storage for messages to be sent to server while connection to server is not available c eZdZejZdZdS)MessageToSend.Metamessages_to_send_nrN)__name__ __module__ __qualname__rdbdatabasedb_table[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/messages_to_send.pyMetar s;(rrF)nullMessageToSendTztimestamp messagec||j|j|j|j|jSN)selectid timestampmessageorder_by)clss rget_all_orderedzMessageToSend.get_all_ordereds9zz#&#-==FF M36   rc|||j|kS)N)r)updatewhererexecute)r message_idrs r set_messagezMessageToSend.set_messages; JJwJ ' ' - -cf .B C C K K M M rc||j|}|Sr)deleter$rin_r%)r queryqs r delete_inzMessageToSend.delete_in$s9 JJLL  svzz%00 1 1yy{{rc&||j|}||j|}|Sr) rrrlimitr)r$rr*r%)r r0oldr,s r delete_oldzMessageToSend.delete_old)sbjjll##CM2288?? JJLL  svzz# / /yy{{rreturnNc tdt|dD]G}fd|||dzD}tj|fi|HdS)NrdcHg|]}j|Sr)r_asdict).0rowr s r z-MessageToSend.insert_many..3s=7:""C(0022r)rangelensuper insert_manyr%)r rowskwargsidata __class__s` rr>zMessageToSend.insert_many/sq#d))S)) : :A>B1q3w;>OD EGG  / / / / 7 7 9 9 9 9  : :r)r.)r3N)r rr__doc__rrrrrrr classmethodr!r'r-r2r> __classcell__)rCs@rr r s# ))))))))  &&&IiU###GZ 02EFFN  [   [ [[ :::::[:::::rr N) collectionsrpeeweerrdefence360agent.modelrrr rrrrJs{""""""((((((((11111111.:.:.:.:.:E.:.:.:.:.:rdefence360agent/model/__pycache__/messages_to_send.cpython-311.pyc0000644000000000000000000001002700000000000022102 0ustar r_jCNddlmZddlmZmZddlmZmZGddeZdS)) namedtuple) FloatField BlobField)instanceModelceZdZdZGddZedZedZe ddZ e dZ e d Z e d Ze dd Ze dfd ZxZS) MessageToSendzc Storage for messages to be sent to server while connection to server is not available c eZdZejZdZdS)MessageToSend.Metamessages_to_send_nrN)__name__ __module__ __qualname__rdbdatabasedb_table[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/messages_to_send.pyMetar s;(rrF)nullMessageToSendTztimestamp messagec||j|j|j|j|jSN)selectid timestampmessageorder_by)clss rget_all_orderedzMessageToSend.get_all_ordereds9zz#&#-==FF M36   rc|||j|kS)N)r)updatewhererexecute)r message_idrs r set_messagezMessageToSend.set_messages; JJwJ ' ' - -cf .B C C K K M M rc||j|}|Sr)deleter$rin_r%)r queryqs r delete_inzMessageToSend.delete_in$s9 JJLL  svzz%00 1 1yy{{rc&||j|}||j|}|Sr) rrrlimitr)r$rr*r%)r r0oldr,s r delete_oldzMessageToSend.delete_old)sbjjll##CM2288?? JJLL  svzz# / /yy{{rreturnNc tdt|dD]G}fd|||dzD}tj|fi|HdS)NrdcHg|]}j|Sr)r_asdict).0rowr s r z-MessageToSend.insert_many..3s=7:""C(0022r)rangelensuper insert_manyr%)r rowskwargsidata __class__s` rr>zMessageToSend.insert_many/sq#d))S)) : :A>B1q3w;>OD EGG  / / / / 7 7 9 9 9 9  : :r)r.)r3N)r rr__doc__rrrrrrr classmethodr!r'r-r2r> __classcell__)rCs@rr r s# ))))))))  &&&IiU###GZ 02EFFN  [   [ [[ :::::[:::::rr N) collectionsrpeeweerrdefence360agent.modelrrr rrrrJs{""""""((((((((11111111.:.:.:.:.:E.:.:.:.:.:rdefence360agent/model/__pycache__/simplification.cpython-311.opt-1.pyc0000644000000000000000000002304100000000000022531 0ustar r_j BddlZddlZddlZddlZddlmZmZmZmZm Z m Z ddl m Z m Z dZejeZGddeZGddeZGd d e Zd Zd e d ededefdZGdde ZdZdZGddZeZdS)N) BlobField CharField DateFieldForeignKeyField IntegerFieldPeeweeException)instanceModeliQceZdZdZdZdZdS) FilenameFieldz/ Class to store file names in database c*tj|SN)osfsencodeselfvalues Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/simplification.pydb_valuezFilenameField.db_value{5!!!c*tj|Sr)rfsdecoders r python_valuezFilenameField.python_valuerrN)__name__ __module__ __qualname____doc__rrrrr r s<""""""""rr ceZdZdZdZdS) ScanPathField list_of_filesc>t|tr|jS|Sr) isinstancelistREALTIME_SCAN_PATH_STUBrs rrzScanPathField.db_value&s" eT " " 0/ / rN)rrrr&rrrrr!r!#s)-rr!ceZdZdZdS) ModelErrorzf Model exception. Please use this one from other modules instead PeeweeException directly N)rrrrrrrr(r(,s  Drr(cK||S)z0 Fake run_in_executor() test (DEF-4541) r)loopcbargss rrun_in_executorr-5s 2t9rtablenum_days max_countreturnc4t|dd}|s"td|tj|tzz }||j|j | |j|k}| |j | }|S)z Removes records that is older that *num_days* days and all others that are out of range *max_count* from *table*. Returns count of rows deleted. timestampFz#No 'timestamp' column in table {!r})getattr ValueErrorformattime POSIX_DAYselectr3order_bydesclimitwheredeletenot_inexecute)r.r/r0 has_timestamp end_save_timeto_keep deleted_counts rremove_old_and_truncaterE<sE;66M N>EEeLLMMMIKK(Y"66M U_%% %/&&(( ) ) y   u. / /    U_33G<<==EEGG rceZdZdZGddZedZeddZe de fd Z e d d Z dS) EulazKeeps track of updates and acceptions of end user license agreement. Admins will be asked to accept EULA if the latest version is not accepted yet. c eZdZejZdZdS) Eula.MetaeulaN)rrrr dbdatabasedb_tablerrrMetarI^s;rrNT) primary_keyN)nulldefaultr1ctt||j|jdd}|duS)N) nextiterr9r=acceptedis_nullr:updatedr<)cls unaccepteds r is_acceptedzEula.is_acceptedgsp  s|++--..#+&&q      T!!rc|tj|jdS)N)rV)updater7r=rVrWr@)rYs racceptz Eula.accepttsI DIKK ((.. L " "  ')))))r)r1N) rrrrrNrrXrrV classmethodboolr[r^rrrrGrGWs  iD)))G|t444H "D " " "[ "[rrGcBdtj|dDS)Ncg|]\}}|Srr).0_objs r zget_models..|s,    As    rcjtj|ot|to |tkSr)inspectisclass issubclassr )res rzget_models..s1,,3&&u r)rh getmembersmodules r get_modelsro{s=  (        rctjtjt |ddS)NT)safe)r rKconnect create_tablesrorms rrsrss? K Kj00t<<<< rOrderByNcSrr)nodessrrkz,ApplyOrderBy.resolve_nodes..s%r)r$r rel_modelr4)_modelrvmodelcustom_order_bynoderzs @r resolve_nodeszApplyOrderBy.resolve_nodess!+6? C C OF   !%D99  &HGO[----HHJJE 5+t44D r column_namesc|d|dd}}t||}g}|D]M}|r4t||D]}||8||N|S)z :param model: peewee.Model or peewee.ForeignKeyField :param column_names: list :return: list rrSN)rur get_nodesappend)r}rrvrestrzresult node_or_modelrs rrzApplyOrderBy.get_nodess)O\!""-=T **5+>>" - -M -(22=$GG((DMM$''''( m,,,, rcg}|D]j}t||jd}|D]2}||jr|n|3k|j|S)z :param order_by: list :param model: peewee.Model or peewee.ForeignKeyField :param query_builder: peewee.Query :return: peewee.Query with applied order_by .)rurrvsplitrr;r:)rr:r} query_builderordersorderrzrs r__call__zApplyOrderBy.__call__s C CE **5%2C2I2I#2N2NOOE C C UZAdiikkkTBBBB C&}%v..rN) rrr staticmethodstrtuplerr%rrrrrrurus}35\&t\$ / / / / /rru)rhloggingrr7peeweerrrrrrdefence360agent.modelr r r8 getLoggerrloggerr r!r(r-intrErGrorsruapply_order_byrrrrs 21111111   8 $ $ " " " " "I " " "I         ,/6!!!!!5!!!H   === 5/5/5/5/5/5/5/5/prdefence360agent/model/__pycache__/simplification.cpython-311.pyc0000644000000000000000000002304100000000000021572 0ustar r_j BddlZddlZddlZddlZddlmZmZmZmZm Z m Z ddl m Z m Z dZejeZGddeZGddeZGd d e Zd Zd e d ededefdZGdde ZdZdZGddZeZdS)N) BlobField CharField DateFieldForeignKeyField IntegerFieldPeeweeException)instanceModeliQceZdZdZdZdZdS) FilenameFieldz/ Class to store file names in database c*tj|SN)osfsencodeselfvalues Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/simplification.pydb_valuezFilenameField.db_value{5!!!c*tj|Sr)rfsdecoders r python_valuezFilenameField.python_valuerrN)__name__ __module__ __qualname____doc__rrrrr r s<""""""""rr ceZdZdZdZdS) ScanPathField list_of_filesc>t|tr|jS|Sr) isinstancelistREALTIME_SCAN_PATH_STUBrs rrzScanPathField.db_value&s" eT " " 0/ / rN)rrrr&rrrrr!r!#s)-rr!ceZdZdZdS) ModelErrorzf Model exception. Please use this one from other modules instead PeeweeException directly N)rrrrrrrr(r(,s  Drr(cK||S)z0 Fake run_in_executor() test (DEF-4541) r)loopcbargss rrun_in_executorr-5s 2t9rtablenum_days max_countreturnc4t|dd}|s"td|tj|tzz }||j|j | |j|k}| |j | }|S)z Removes records that is older that *num_days* days and all others that are out of range *max_count* from *table*. Returns count of rows deleted. timestampFz#No 'timestamp' column in table {!r})getattr ValueErrorformattime POSIX_DAYselectr3order_bydesclimitwheredeletenot_inexecute)r.r/r0 has_timestamp end_save_timeto_keep deleted_counts rremove_old_and_truncaterE<sE;66M N>EEeLLMMMIKK(Y"66M U_%% %/&&(( ) ) y   u. / /    U_33G<<==EEGG rceZdZdZGddZedZeddZe de fd Z e d d Z dS) EulazKeeps track of updates and acceptions of end user license agreement. Admins will be asked to accept EULA if the latest version is not accepted yet. c eZdZejZdZdS) Eula.MetaeulaN)rrrr dbdatabasedb_tablerrrMetarI^s;rrNT) primary_keyN)nulldefaultr1ctt||j|jdd}|duS)N) nextiterr9r=acceptedis_nullr:updatedr<)cls unaccepteds r is_acceptedzEula.is_acceptedgsp  s|++--..#+&&q      T!!rc|tj|jdS)N)rV)updater7r=rVrWr@)rYs racceptz Eula.accepttsI DIKK ((.. L " "  ')))))r)r1N) rrrrrNrrXrrV classmethodboolr[r^rrrrGrGWs  iD)))G|t444H "D " " "[ "[rrGcBdtj|dDS)Ncg|]\}}|Srr).0_objs r zget_models..|s,    As    rcjtj|ot|to |tkSr)inspectisclass issubclassr )res rzget_models..s1,,3&&u r)rh getmembersmodules r get_modelsro{s=  (        rctjtjt |ddS)NT)safe)r rKconnect create_tablesrorms rrsrss? K Kj00t<<<< rOrderByNcSrr)nodessrrkz,ApplyOrderBy.resolve_nodes..s%r)r$r rel_modelr4)_modelrvmodelcustom_order_bynoderzs @r resolve_nodeszApplyOrderBy.resolve_nodess!+6? C C OF   !%D99  &HGO[----HHJJE 5+t44D r column_namesc|d|dd}}t||}g}|D]M}|r4t||D]}||8||N|S)z :param model: peewee.Model or peewee.ForeignKeyField :param column_names: list :return: list rrSN)rur get_nodesappend)r}rrvrestrzresult node_or_modelrs rrzApplyOrderBy.get_nodess)O\!""-=T **5+>>" - -M -(22=$GG((DMM$''''( m,,,, rcg}|D]j}t||jd}|D]2}||jr|n|3k|j|S)z :param order_by: list :param model: peewee.Model or peewee.ForeignKeyField :param query_builder: peewee.Query :return: peewee.Query with applied order_by .)rurrvsplitrr;r:)rr:r} query_builderordersorderrzrs r__call__zApplyOrderBy.__call__s C CE **5%2C2I2I#2N2NOOE C C UZAdiikkkTBBBB C&}%v..rN) rrr staticmethodstrtuplerr%rrrrrrurus}35\&t\$ / / / / /rru)rhloggingrr7peeweerrrrrrdefence360agent.modelr r r8 getLoggerrloggerr r!r(r-intrErGrorsruapply_order_byrrrrs 21111111   8 $ $ " " " " "I " " "I         ,/6!!!!!5!!!H   === 5/5/5/5/5/5/5/5/prdefence360agent/model/__pycache__/tls_check.cpython-311.opt-1.pyc0000644000000000000000000001107300000000000021460 0ustar r_jddlZddlZddlZddlZddlmZddlmZGddeZ ej e Z ej ZdZGddZGd d eZd d Zd ZdS)N)SqliteExtDatabase)gceZdZdZdS)OverridingResetz Overriding reset could be a signal of logic error thus need to be explicitly handled in all places where this exception is expected to occur. N)__name__ __module__ __qualname____doc__T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/tls_check.pyrr s  Dr rg@c&eZdZdefdZdZdZdS) _TimedAtomicinnerc0||_d|_d|_dS)Ng)_inner_start_caller)selfrs r __init__z_TimedAtomic.__init__s    r ctj|_dt jddd|_|jS)Nr)limit) time monotonicrjoin traceback format_stackrr __enter__)rs r r!z_TimedAtomic.__enter__!sOn&& wwy5A>>>ssCDD {$$&&&r c|jj|}tj|jz }|t kr!t d||j|S)Nz"Slow transaction held for %.2fs %s) r__exit__rrr_SLOW_TXN_THRESHOLD_Sloggerwarningr)rargsresultelapseds r r#z_TimedAtomic.__exit__&s[%%t,.""T[0 * * * NN5      r N)rrr objectrr!r#r r r rrsMf '''     r rc0eZdZfdZddeffd ZxZS)SqliteDatabaseWrappercNt|i|tj|i|SN) _validatesuper execute_sql)rr'kwargs __class__s r r1z!SqliteDatabaseWrapper.execute_sql3s24"6""""uww"D3F333r IMMEDIATE lock_typect|}tjdrt |S|S)NDEBUG)r0atomicrgetr)rr5rr3s r r8zSqliteDatabaseWrapper.atomic7s;y)) 5>> '&& & r )r4)rrr r1strr8 __classcell__)r3s@r r,r,2sb44444r r,cttdrt|ptjt_dS)Nthread_ident_memo)hasattr_thread_local_storager threading get_identr=) new_values r resetrC>sC$&9::  *Y(**+++r cttdd}|tddS|t jkr1td|t j||dSdS)Nr=z7wrong thread or _validate() was not preceded by reset()zFthread_ident_memo check failed [%r != %r] context: args: %s kwargs: %s)getattrr?r%errorr@rA)r'r2r=s r r/r/Gs2D  NOOOOO i133 3 3  -    ! !         4 3r r.)loggingr@rrplayhouse.sqlite_extr&defence360agent.internals.global_scoper Exceptionr getLoggerrr%localr?r$rr,rCr/r r r rMs' 222222444444     i     8 $ $' )).     -        r defence360agent/model/__pycache__/tls_check.cpython-311.pyc0000644000000000000000000001107300000000000020521 0ustar r_jddlZddlZddlZddlZddlmZddlmZGddeZ ej e Z ej ZdZGddZGd d eZd d Zd ZdS)N)SqliteExtDatabase)gceZdZdZdS)OverridingResetz Overriding reset could be a signal of logic error thus need to be explicitly handled in all places where this exception is expected to occur. N)__name__ __module__ __qualname____doc__T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/tls_check.pyrr s  Dr rg@c&eZdZdefdZdZdZdS) _TimedAtomicinnerc0||_d|_d|_dS)Ng)_inner_start_caller)selfrs r __init__z_TimedAtomic.__init__s    r ctj|_dt jddd|_|jS)Nr)limit) time monotonicrjoin traceback format_stackrr __enter__)rs r r!z_TimedAtomic.__enter__!sOn&& wwy5A>>>ssCDD {$$&&&r c|jj|}tj|jz }|t kr!t d||j|S)Nz"Slow transaction held for %.2fs %s) r__exit__rrr_SLOW_TXN_THRESHOLD_Sloggerwarningr)rargsresultelapseds r r#z_TimedAtomic.__exit__&s[%%t,.""T[0 * * * NN5      r N)rrr objectrr!r#r r r rrsMf '''     r rc0eZdZfdZddeffd ZxZS)SqliteDatabaseWrappercNt|i|tj|i|SN) _validatesuper execute_sql)rr'kwargs __class__s r r1z!SqliteDatabaseWrapper.execute_sql3s24"6""""uww"D3F333r IMMEDIATE lock_typect|}tjdrt |S|S)NDEBUG)r0atomicrgetr)rr5rr3s r r8zSqliteDatabaseWrapper.atomic7s;y)) 5>> '&& & r )r4)rrr r1strr8 __classcell__)r3s@r r,r,2sb44444r r,cttdrt|ptjt_dS)Nthread_ident_memo)hasattr_thread_local_storager threading get_identr=) new_values r resetrC>sC$&9::  *Y(**+++r cttdd}|tddS|t jkr1td|t j||dSdS)Nr=z7wrong thread or _validate() was not preceded by reset()zFthread_ident_memo check failed [%r != %r] context: args: %s kwargs: %s)getattrr?r%errorr@rA)r'r2r=s r r/r/Gs2D  NOOOOO i133 3 3  -    ! !         4 3r r.)loggingr@rrplayhouse.sqlite_extr&defence360agent.internals.global_scoper Exceptionr getLoggerrr%localr?r$rr,rCr/r r r rMs' 222222444444     i     8 $ $' )).     -        r defence360agent/model/__pycache__/wordpress.cpython-311.opt-1.pyc0000644000000000000000000000632000000000000021550 0ustar r_j~ddlmZddlmZddlmZmZmZmZddl m Z m Z GddeZ Gdde Z d S) ) annotations) NamedTuple) CharField FloatField IntegerFieldTimestampField)instanceModelcheZdZUded<ded<ded<dZded<edd Zdd Zd ZdZ dS)WPSitestrdocrootdomainintuid1.0.0versionsite WordpressSitereturncH||j|j|j|jS)z7Create a WPSite instance from a WordpressSite instance.rrrrr)clsrs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress.pyfrom_wordpress_sitezWPSite.from_wordpress_sites1sL;L     cFt|j|j|j|S)z5Create a new WPSite instance with an updated version.r)r rrr)selfrs rbuild_with_versionzWPSite.build_with_versions+L;     rct|tstS|j|j|jf|j|j|jfkSN) isinstancer NotImplementedrrr)rothers r__eq__z WPSite.__eq__!sH%(( "! ! dk484 M L I9   rcDt|j|j|jfSr!)hashrrr)rs r__hash__zWPSite.__hash__+sT\4;9:::rN)rrrr )rr rr ) __name__ __module__ __qualname____annotations__r classmethodrrr%r(rrr r sLLLKKK HHHG   [        ;;;;;rr ceZdZGddZeddZedZedZe ddZ ed dZ e dd Z dS) rc eZdZejZdZdS)WordpressSite.Metawordpress_siteN)r)r*r+r dbdatabasedb_tabler.rrMetar10s;#rr6TF) primary_keynull)r8N)defaultr8r)r8r9)r)r*r+r6rrrrrrmanually_deleted_atrrdisabled_rules_sync_tsr.rrrr/s$$$$$$$$iDu555G YE " " "F ,E " " "C(.DAAAie444G'ZT4@@@rrN) __future__rtypingrpeeweerrrrdefence360agent.modelr r r rr.rrr@s""""""FFFFFFFFFFFF11111111$;$;$;$;$;Z$;$;$;N A A A A AE A A A A Ardefence360agent/model/__pycache__/wordpress.cpython-311.pyc0000644000000000000000000000632000000000000020611 0ustar r_j~ddlmZddlmZddlmZmZmZmZddl m Z m Z GddeZ Gdde Z d S) ) annotations) NamedTuple) CharField FloatField IntegerFieldTimestampField)instanceModelcheZdZUded<ded<ded<dZded<edd Zdd Zd ZdZ dS)WPSitestrdocrootdomainintuid1.0.0versionsite WordpressSitereturncH||j|j|j|jS)z7Create a WPSite instance from a WordpressSite instance.rrrrr)clsrs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress.pyfrom_wordpress_sitezWPSite.from_wordpress_sites1sL;L     cFt|j|j|j|S)z5Create a new WPSite instance with an updated version.r)r rrr)selfrs rbuild_with_versionzWPSite.build_with_versions+L;     rct|tstS|j|j|jf|j|j|jfkSN) isinstancer NotImplementedrrr)rothers r__eq__z WPSite.__eq__!sH%(( "! ! dk484 M L I9   rcDt|j|j|jfSr!)hashrrr)rs r__hash__zWPSite.__hash__+sT\4;9:::rN)rrrr )rr rr ) __name__ __module__ __qualname____annotations__r classmethodrrr%r(rrr r sLLLKKK HHHG   [        ;;;;;rr ceZdZGddZeddZedZedZe ddZ ed dZ e dd Z dS) rc eZdZejZdZdS)WordpressSite.Metawordpress_siteN)r)r*r+r dbdatabasedb_tabler.rrMetar10s;#rr6TF) primary_keynull)r8N)defaultr8r)r8r9)r)r*r+r6rrrrrrmanually_deleted_atrrdisabled_rules_sync_tsr.rrrr/s$$$$$$$$iDu555G YE " " "F ,E " " "C(.DAAAie444G'ZT4@@@rrN) __future__rtypingrpeeweerrrrdefence360agent.modelr r r rr.rrr@s""""""FFFFFFFFFFFF11111111$;$;$;$;$;Z$;$;$;N A A A A AE A A A A Ardefence360agent/model/__pycache__/wordpress_incident.cpython-311.opt-1.pyc0000644000000000000000000006340200000000000023431 0ustar r_jM"dZddlZddlZddlZddlmZddlmZmZddl m Z ddl m Z m Z ddlmZmZmZmZmZmZmZddlmZmZdd lmZdd lmZmZdd lmZdd l m!Z!dd l"m#Z#m$Z$ej%e&Z'e(Z)dZ*dZ+dZ,dZ-GddeZ.de/de/de/fdZ0e)fde/de/de/fdZ1edZ2de3dzde3dzfdZ4de/de/de.fdZ5de6e/de6e/fdZ7de.de/fd Z8 d=d#e9d$e9d%e9dzd&e3dzd'e3dzd(e3dzd)e3dzd*e3dzd+e9dzd,e9dzd-e6dzd.e:fd/Z;d0e6e/de9fd1Z< d>d#e9d3e e9de6e/fd4Z=d5e e9e9fde9fd6Z>e-fd7e9d#e9dzfd8Z?de/de3fd9Z@d:e3dzde9fd;ZAde3dzfd<ZBdS)?a0Helper functions for WordPress CVE protection incidents. WordPress incidents are stored in a dedicated wordpress_incident table with plugin-specific data stored in the extra_info JSON field. This module provides helper functions to work with WordPress incidents. Available for both AV and IM360 modes. N) defaultdict) ExitStackcontextmanager) timedelta)IterableMapping)EXCLUDEDSQL CharField FloatField IntegerField TextFieldfn) JSONFieldr)geo)Modelinstance)apply_order_by)OrderBy)CHUNK_SIZE_SQL_QUERYsplit_for_chunk<)abusernamepluginruleseveritydomainbucket2iceZdZdZeddZedZedZe dZ edZ edZ edZ edZedZeddZeddZedZedZed d ded g ZGd dZdS)WordpressIncidentaI WordPress incident model for CVE protection. Uses dedicated wordpress_incident table created in migration 191. Repeats of the same attack are aggregated per minute: the unique constraint on (abuser, name, plugin, rule, severity, domain, bucket) keeps one row per aggregation window, counted by retries. T) primary_keynull)r$ country_id)r$ column_nameN)r$defaultFrz DEFAULT 0)r$r'index constraintsc*eZdZejZdZedffZdS)WordpressIncident.Metawordpress_incidentTN) __name__ __module__ __qualname__rdbdatabasedb_table AGGREGATE_KEYindexes]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress_incident.pyMetar+^s';'!4(*r6r8)r-r.r/__doc__r idr rrr timestampretriesrrr descriptionrcountryrr extra_inforr unsent_retriesr8r5r6r7r"r":si $T 2 2 2B YD ! ! !F 9$   D %%%Il%%%G|&&&H 9$   D)&&&K YD ! ! !FiT|<<??  M%%j11  ]&&{33  }((77! "  )).99# $  1 12H I I% & =,,->??' (&)).99#%!%%j113   r6c |dpt|}t||}|dp|d}|tur7t 5}t ||}dddn #1swxYwYnt ||}t |dd}d|dd |t|tzd t|d d |d d||||d|d S)a Build complete incident dict ready for database insertion. This is used for both single incident creation and bulk insertion. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username, user_id) geo_reader: An open geo Reader (or None) to resolve the abuser country. Pass one from country_reader() when building many incidents in a loop to avoid reopening the mmdb per incident. When omitted, a short-lived reader is opened for this single call. Returns: Dict with all fields ready for Incident.create() or bulk insert message REMOTE_ADDR attacker_ipNtsr wordpressrule_idunknownrIzWordPress CVE: rHUnknownr) rrr;rr<rrr=rr>rr?) ribuild_message_fallbackrm_UNSETcountry_reader _country_codefloatintBUCKET_SECONDScalculate_severity) rArB geo_readerror? abuser_ipreaderr>r;s r7build_incident_dictrs& **.D//G"-;;J!!-00M4E4E55IV    7#FI66G 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 I66m''a0011I!!)Y77i>122&}'8'8'@'@AAG-"3"3E9"E"EGG--))   s6BBBc#4Kt5} |tj}nB#t$r5}t d|dVYd}~ddddSd}~wwxYw|VddddS#1swxYwYdS)zYield an open geo Reader, or None when the mmdb can't be opened. Lets bulk callers open the mmap'd reader once instead of per incident, while keeping enrichment non-blocking when the geo bundle is missing. zGeoIP reader unavailable: %sN)r enter_contextrr Exceptionloggerdebug)stackrexcs r7rzrzs   ((66FF    LL7 = = =JJJ FFF   s7B &:B  A9A4#B 4A99B  BBipc||sdS ||S#t$r'}td||Yd}~dSd}~wwxYw)NzGeoIP lookup failed for %s: %s)get_coderrr)rrrs r7r{r{sj ~R~tr"""  5r3???ttttts AA  AcDt||}tjdi|S)aD Create a WordPress incident in the wordpress_incident table. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username) Returns: WordpressIncident instance with WordPress fields populated in extra_info r5)rr"create)rArB incident_dicts r7create_wordpress_incidentrs*( yAAM  # 4 4m 4 44r6incident_dictscRi}|D]tfdtD}||}|t||<L|dxxdz cc<t |dd|d<t |S)zACollapse incidents sharing an aggregate key into one counted row.c3(K|] }|V dS)Nr5).0fieldrs r7 z+aggregate_incident_dicts..s(DDUM%(DDDDDDr6Nr<r;)tupler3ridictminlistvalues)rgroupskeygrouprs @r7aggregate_incident_dictsrs!#F'   DDDDmDDDDD 3 =}--F3K  iM)44 +  k :  k    r6incidentc |j|j|j|j|j|j|j|j|j|j |j |j d S)z Convert a WordpressIncident model instance to a dictionary. Args: incident: WordpressIncident model instance Returns: Dictionary representation of the incident r:rrr;r<rrr=rr>rr?r)rs r7wordpress_incident_to_dictrsUk/ '#% +/#/)   r6FlimitoffsetrO by_abuser_ipby_country_code by_domainsearch site_searchsincetoorder_byinclude_hiddenc tttjdk} | sR| tjtjdz} |6| tjtj d|k} |2| tj |} |#| tj |k} |2| tj |} || tj |tj |ztj |ztj |z} |6| tjtj d|k} |6| tjd|k} | 6| tjd| k} | pg} | D]T}t%|t&r(| t+j|?| |Ut/| t| } n1| tj} | |} | |} d| DS)a Get WordPress incidents as dictionaries. Args: limit: Maximum number of incidents to return offset: Offset for pagination user_id: Filter by user ID (None = all) by_abuser_ip: Filter by abuser IP address (None = all) by_country_code: Filter by country code (None = all) by_domain: Filter by domain (None = all) search: Search in IP address, name, description, or domain (None = all) site_search: Filter by site path in extra_info (None = all) since: Filter by timestamp >= this value (unix timestamp, None = all) to: Filter by timestamp <= this value (unix timestamp, None = all) order_by: List of fields to order by (None = default order by timestamp desc). Can be either strings (e.g., ["timestamp+", "severity-"]) or OrderBy objects. Strings are automatically converted. include_hidden: When False (default), exclude incidents whose rule has the TEST- prefix (internal probe rules that the WordPress plugin hides from its admin UI). Returns: List of incident dictionaries rszTEST-Nz $.user_idz $.site_pathREALc,g|]}t|Sr5)r)rincs r7 z+get_wordpress_incidents..s! G G G &s + + G G Gr6)r"selectwhererris_null startswithr json_extractr?rcontainsr>rrr=r;cast isinstancestrappendr fromstringrrdescrrexecute)rrrOrrrrrrrrrqueryconverted_order_byitems r7get_wordpress_incidentsr sL  $ $%6 7 7 = =  ![ 0  E    " * * , , %00999 :    O-8+ F F     -4==lKKLL" -5HII -4==iHHII   " + +F 3 3+44V<< =&//77 8 &//77 8   O-8- H H      -7<.s0.3!5))r6cHg|]}i|d|dpdi S)r@r<rv)ri)rrs r7rz3bulk_create_wordpress_incidents..sJ     E8D%x||I'>'>'C!DD   r6)conflict_targetupdateN)r3r"_metar1atomicrangelenINSERT_CHUNK_SIZE insert_many on_conflictr<r r@r; peewee_fnMINr)rrrowsstartchunks r7bulk_create_wordpress_incidentsrs q7DO   &   D  ) 0 0 2 21c$ii):;;  E):!::;E  ) )% 0 0 < < /%-)1H4DD &4)88;KK%/)3X5G22  =   giiii% * ~  sC3EE  E r5 exclude_idscJt|}tttjdktjdkztjtj  |t|z}g}|D]N}|j |vr | it|d|jit||krnO|S)zGet incidents carrying occurrences correlation has not acknowledged yet, oldest first. Unlike get_wordpress_incidents() this keeps TEST- rules: they are hidden from the WordPress admin UI but still belong in correlation. rsrr@)setr"rrrr@rr;ascr:rrrr)rrr incidentsrows r7get_unsent_wordpress_incidentsrs/k""K   !233   % 4 /!3 5     ' + + - -/@/C/G/G/I/I   us;''' ( ( I   6[   ,S11  #"4     y>>U " " E # r6reportedc tt}|D]&\}}|dkr|||'d}tjj5|D]\}}t|tD]}|t tj dtj |z tj|z } dddn #1swxYwY|S)zDiscount the occurrences correlation acknowledged. Subtracts instead of clearing so that occurrences merged into a row while its message was in flight stay pending rather than being dropped. r) chunk_size)r@N)rritemsrr"rr1rrrrrMAXr@rr:in_r)r by_amount incident_idamountsettled incident_idsrs r7#settle_wordpress_incidents_reportedrs D!!I'~~//22 V A:: f  $ $[ 1 1 1G  ) 0 0 2 2  $-OO$5$5   FL()=  %,,')v0?&H((- U,/33E::;;WYY                  Ns5B4D66D:=D:daysctjt|z }tjdk}tjd|k}|ottjtj  d | du}|rttj |tj  |}|tj |z}t||zS)N)rrsrrv)timer total_secondsr"rr;rrrrrrscalarr:rnot_indeleter)rr cutoff_time is_wordpressstaleover_capkeeps r7delete_old_wordpress_incidentsrsc)++ t 4 4 4 B B D DDK$+{:L  ' , ,V 4 4{ BE    $ $%6%@ A A X'16688 9 9 U1XX VE]] VXX  3  $ $%6%9 : : UE6]] X'16688 9 9 U5\\ "%,,T222  # # % % + +L5,@ A A I I K KKr6cdg}|dr||d|dr||d|dr||d|dr||d|dr||dd|S)z=Build message if plugin didn't provide one (per spec format).z IM WP plugin:rtrHrKrLrI )rirjoin)rApartss r7rxrxs  E##/ ]9-...+ ]5)***  , ]6*+++##/ ]9-...  , ]6*+++ 88E??r6rIc&|dkrdS|dkrdSdS)z!Calculate severity based on mode.blockpassr5)rIs r7rr.s# wq qqr6c`|dSt|tr|Stj|S)z>Serialize a value to JSON string if it's not already a string.N)rrjsondumps)values r7rhrh8s3 }t% :e  r6) rrNNNNNNNNNF)r5)Cr9loggingrr collectionsr contextlibrrdatetimertypingrrpeeweer r r r r rrrplayhouse.sqlite_extrdefence360agent.internalsrdefence360agent.modelrr$defence360agent.model.simplificationr"defence360agent.rpc_tools.validaterdefence360agent.utilsrr getLoggerr-robjectryr~r3rMAX_STORED_INCIDENTSr"rrmrrzrr{rrrrr}boolrrrrrrxrrhr5r6r7rs ######00000000$$$$$$$$/.......))))))11111111??????666666GGGGGGGG  8 $ $  '+'+'+'+'+'+'+'+T*D*T*d****\6<222$(2 2222j    cDjS4Z55$(55555"!T$Z!DJ!!!!():t6#"& "  dHdH dH dH4ZdH* dH 4Z dH Tz dH $JdHtdH :dH d dHTkdHdHdHdHdHN&DJ&3&&&&V"$(( (#( $Z((((V'#s(2C@$8LL LDjLLLLD$3$S4ZC3:r6defence360agent/model/__pycache__/wordpress_incident.cpython-311.pyc0000644000000000000000000006340200000000000022472 0ustar r_jM"dZddlZddlZddlZddlmZddlmZmZddl m Z ddl m Z m Z ddlmZmZmZmZmZmZmZddlmZmZdd lmZdd lmZmZdd lmZdd l m!Z!dd l"m#Z#m$Z$ej%e&Z'e(Z)dZ*dZ+dZ,dZ-GddeZ.de/de/de/fdZ0e)fde/de/de/fdZ1edZ2de3dzde3dzfdZ4de/de/de.fdZ5de6e/de6e/fdZ7de.de/fd Z8 d=d#e9d$e9d%e9dzd&e3dzd'e3dzd(e3dzd)e3dzd*e3dzd+e9dzd,e9dzd-e6dzd.e:fd/Z;d0e6e/de9fd1Z< d>d#e9d3e e9de6e/fd4Z=d5e e9e9fde9fd6Z>e-fd7e9d#e9dzfd8Z?de/de3fd9Z@d:e3dzde9fd;ZAde3dzfd<ZBdS)?a0Helper functions for WordPress CVE protection incidents. WordPress incidents are stored in a dedicated wordpress_incident table with plugin-specific data stored in the extra_info JSON field. This module provides helper functions to work with WordPress incidents. Available for both AV and IM360 modes. N) defaultdict) ExitStackcontextmanager) timedelta)IterableMapping)EXCLUDEDSQL CharField FloatField IntegerField TextFieldfn) JSONFieldr)geo)Modelinstance)apply_order_by)OrderBy)CHUNK_SIZE_SQL_QUERYsplit_for_chunk<)abusernamepluginruleseveritydomainbucket2iceZdZdZeddZedZedZe dZ edZ edZ edZ edZedZeddZeddZedZedZed d ded g ZGd dZdS)WordpressIncidentaI WordPress incident model for CVE protection. Uses dedicated wordpress_incident table created in migration 191. Repeats of the same attack are aggregated per minute: the unique constraint on (abuser, name, plugin, rule, severity, domain, bucket) keeps one row per aggregation window, counted by retries. T) primary_keynull)r$ country_id)r$ column_nameN)r$defaultFrz DEFAULT 0)r$r'index constraintsc*eZdZejZdZedffZdS)WordpressIncident.Metawordpress_incidentTN) __name__ __module__ __qualname__rdbdatabasedb_table AGGREGATE_KEYindexes]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wordpress_incident.pyMetar+^s';'!4(*r6r8)r-r.r/__doc__r idr rrr timestampretriesrrr descriptionrcountryrr extra_inforr unsent_retriesr8r5r6r7r"r":si $T 2 2 2B YD ! ! !F 9$   D %%%Il%%%G|&&&H 9$   D)&&&K YD ! ! !FiT|<<??  M%%j11  ]&&{33  }((77! "  )).99# $  1 12H I I% & =,,->??' (&)).99#%!%%j113   r6c |dpt|}t||}|dp|d}|tur7t 5}t ||}dddn #1swxYwYnt ||}t |dd}d|dd |t|tzd t|d d |d d||||d|d S)a Build complete incident dict ready for database insertion. This is used for both single incident creation and bulk insertion. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username, user_id) geo_reader: An open geo Reader (or None) to resolve the abuser country. Pass one from country_reader() when building many incidents in a loop to avoid reopening the mmdb per incident. When omitted, a short-lived reader is opened for this single call. Returns: Dict with all fields ready for Incident.create() or bulk insert message REMOTE_ADDR attacker_ipNtsr wordpressrule_idunknownrIzWordPress CVE: rHUnknownr) rrr;rr<rrr=rr>rr?) ribuild_message_fallbackrm_UNSETcountry_reader _country_codefloatintBUCKET_SECONDScalculate_severity) rArB geo_readerror? abuser_ipreaderr>r;s r7build_incident_dictrs& **.D//G"-;;J!!-00M4E4E55IV    7#FI66G 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 I66m''a0011I!!)Y77i>122&}'8'8'@'@AAG-"3"3E9"E"EGG--))   s6BBBc#4Kt5} |tj}nB#t$r5}t d|dVYd}~ddddSd}~wwxYw|VddddS#1swxYwYdS)zYield an open geo Reader, or None when the mmdb can't be opened. Lets bulk callers open the mmap'd reader once instead of per incident, while keeping enrichment non-blocking when the geo bundle is missing. zGeoIP reader unavailable: %sN)r enter_contextrr Exceptionloggerdebug)stackrexcs r7rzrzs   ((66FF    LL7 = = =JJJ FFF   s7B &:B  A9A4#B 4A99B  BBipc||sdS ||S#t$r'}td||Yd}~dSd}~wwxYw)NzGeoIP lookup failed for %s: %s)get_coderrr)rrrs r7r{r{sj ~R~tr"""  5r3???ttttts AA  AcDt||}tjdi|S)aD Create a WordPress incident in the wordpress_incident table. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username) Returns: WordpressIncident instance with WordPress fields populated in extra_info r5)rr"create)rArB incident_dicts r7create_wordpress_incidentrs*( yAAM  # 4 4m 4 44r6incident_dictscRi}|D]tfdtD}||}|t||<L|dxxdz cc<t |dd|d<t |S)zACollapse incidents sharing an aggregate key into one counted row.c3(K|] }|V dS)Nr5).0fieldrs r7 z+aggregate_incident_dicts..s(DDUM%(DDDDDDr6Nr<r;)tupler3ridictminlistvalues)rgroupskeygrouprs @r7aggregate_incident_dictsrs!#F'   DDDDmDDDDD 3 =}--F3K  iM)44 +  k :  k    r6incidentc |j|j|j|j|j|j|j|j|j|j |j |j d S)z Convert a WordpressIncident model instance to a dictionary. Args: incident: WordpressIncident model instance Returns: Dictionary representation of the incident r:rrr;r<rrr=rr>rr?r)rs r7wordpress_incident_to_dictrsUk/ '#% +/#/)   r6FlimitoffsetrO by_abuser_ipby_country_code by_domainsearch site_searchsincetoorder_byinclude_hiddenc tttjdk} | sR| tjtjdz} |6| tjtj d|k} |2| tj |} |#| tj |k} |2| tj |} || tj |tj |ztj |ztj |z} |6| tjtj d|k} |6| tjd|k} | 6| tjd| k} | pg} | D]T}t%|t&r(| t+j|?| |Ut/| t| } n1| tj} | |} | |} d| DS)a Get WordPress incidents as dictionaries. Args: limit: Maximum number of incidents to return offset: Offset for pagination user_id: Filter by user ID (None = all) by_abuser_ip: Filter by abuser IP address (None = all) by_country_code: Filter by country code (None = all) by_domain: Filter by domain (None = all) search: Search in IP address, name, description, or domain (None = all) site_search: Filter by site path in extra_info (None = all) since: Filter by timestamp >= this value (unix timestamp, None = all) to: Filter by timestamp <= this value (unix timestamp, None = all) order_by: List of fields to order by (None = default order by timestamp desc). Can be either strings (e.g., ["timestamp+", "severity-"]) or OrderBy objects. Strings are automatically converted. include_hidden: When False (default), exclude incidents whose rule has the TEST- prefix (internal probe rules that the WordPress plugin hides from its admin UI). Returns: List of incident dictionaries rszTEST-Nz $.user_idz $.site_pathREALc,g|]}t|Sr5)r)rincs r7 z+get_wordpress_incidents..s! G G G &s + + G G Gr6)r"selectwhererris_null startswithr json_extractr?rcontainsr>rrr=r;cast isinstancestrappendr fromstringrrdescrrexecute)rrrOrrrrrrrrrqueryconverted_order_byitems r7get_wordpress_incidentsr sL  $ $%6 7 7 = =  ![ 0  E    " * * , , %00999 :    O-8+ F F     -4==lKKLL" -5HII -4==iHHII   " + +F 3 3+44V<< =&//77 8 &//77 8   O-8- H H      -7<.s0.3!5))r6cHg|]}i|d|dpdi S)r@r<rv)ri)rrs r7rz3bulk_create_wordpress_incidents..sJ     E8D%x||I'>'>'C!DD   r6)conflict_targetupdateN)r3r"_metar1atomicrangelenINSERT_CHUNK_SIZE insert_many on_conflictr<r r@r; peewee_fnMINr)rrrowsstartchunks r7bulk_create_wordpress_incidentsrs q7DO   &   D  ) 0 0 2 21c$ii):;;  E):!::;E  ) )% 0 0 < < /%-)1H4DD &4)88;KK%/)3X5G22  =   giiii% * ~  sC3EE  E r5 exclude_idscJt|}tttjdktjdkztjtj  |t|z}g}|D]N}|j |vr | it|d|jit||krnO|S)zGet incidents carrying occurrences correlation has not acknowledged yet, oldest first. Unlike get_wordpress_incidents() this keeps TEST- rules: they are hidden from the WordPress admin UI but still belong in correlation. rsrr@)setr"rrrr@rr;ascr:rrrr)rrr incidentsrows r7get_unsent_wordpress_incidentsrs/k""K   !233   % 4 /!3 5     ' + + - -/@/C/G/G/I/I   us;''' ( ( I   6[   ,S11  #"4     y>>U " " E # r6reportedc tt}|D]&\}}|dkr|||'d}tjj5|D]\}}t|tD]}|t tj dtj |z tj|z } dddn #1swxYwY|S)zDiscount the occurrences correlation acknowledged. Subtracts instead of clearing so that occurrences merged into a row while its message was in flight stay pending rather than being dropped. r) chunk_size)r@N)rritemsrr"rr1rrrrrMAXr@rr:in_r)r by_amount incident_idamountsettled incident_idsrs r7#settle_wordpress_incidents_reportedrs D!!I'~~//22 V A:: f  $ $[ 1 1 1G  ) 0 0 2 2  $-OO$5$5   FL()=  %,,')v0?&H((- U,/33E::;;WYY                  Ns5B4D66D:=D:daysctjt|z }tjdk}tjd|k}|ottjtj  d | du}|rttj |tj  |}|tj |z}t||zS)N)rrsrrv)timer total_secondsr"rr;rrrrrrscalarr:rnot_indeleter)rr cutoff_time is_wordpressstaleover_capkeeps r7delete_old_wordpress_incidentsrsc)++ t 4 4 4 B B D DDK$+{:L  ' , ,V 4 4{ BE    $ $%6%@ A A X'16688 9 9 U1XX VE]] VXX  3  $ $%6%9 : : UE6]] X'16688 9 9 U5\\ "%,,T222  # # % % + +L5,@ A A I I K KKr6cdg}|dr||d|dr||d|dr||d|dr||d|dr||dd|S)z=Build message if plugin didn't provide one (per spec format).z IM WP plugin:rtrHrKrLrI )rirjoin)rApartss r7rxrxs  E##/ ]9-...+ ]5)***  , ]6*+++##/ ]9-...  , ]6*+++ 88E??r6rIc&|dkrdS|dkrdSdS)z!Calculate severity based on mode.blockpassr5)rIs r7rr.s# wq qqr6c`|dSt|tr|Stj|S)z>Serialize a value to JSON string if it's not already a string.N)rrjsondumps)values r7rhrh8s3 }t% :e  r6) rrNNNNNNNNNF)r5)Cr9loggingrr collectionsr contextlibrrdatetimertypingrrpeeweer r r r r rrrplayhouse.sqlite_extrdefence360agent.internalsrdefence360agent.modelrr$defence360agent.model.simplificationr"defence360agent.rpc_tools.validaterdefence360agent.utilsrr getLoggerr-robjectryr~r3rMAX_STORED_INCIDENTSr"rrmrrzrr{rrrrr}boolrrrrrrxrrhr5r6r7rs ######00000000$$$$$$$$/.......))))))11111111??????666666GGGGGGGG  8 $ $  '+'+'+'+'+'+'+'+T*D*T*d****\6<222$(2 2222j    cDjS4Z55$(55555"!T$Z!DJ!!!!():t6#"& "  dHdH dH dH4ZdH* dH 4Z dH Tz dH $JdHtdH :dH d dHTkdHdHdHdHdHN&DJ&3&&&&V"$(( (#( $Z((((V'#s(2C@$8LL LDjLLLLD$3$S4ZC3:r6defence360agent/model/__pycache__/wp_disabled_rule.cpython-311.opt-1.pyc0000644000000000000000000004335200000000000023032 0ustar r_j<dZddlmZddlZddlZddlmZmZmZm Z m Z m Z ddl m Z mZejeZGdde Zdeed dfd ZdS) aWordPress-specific disabled rules data model. This module provides a separate data model for WordPress disabled rules, independent of the existing DisabledRule/DisabledRuleDomain models used by modsec/ossec plugins. Disable Behavior: Global and domain-level disables are independent and can coexist. A rule is considered effectively disabled for a given WordPress domain if EITHER of these conditions is true: - A global disable exists for the rule (applies to all domains) - A domain-specific disable exists for the rule and that domain Enabling a rule at one scope does not affect disables at the other scope. For example, removing a global disable leaves any domain-specific disables intact, and vice versa. )IteratorN) CharField FloatField IntegerFieldIntegrityErrorPrimaryKeyFieldfn)Modelinstancec eZdZdZGddZeZedZedZ edZ e dZ edZ edZdZdZd Zd Ze d$d ed eed zdededed zdef dZed ededededef dZed ed eededededef dZed ededed zdedededefdZed ed eed zdefdZed$d eded zdefdZe d%dededeefdZ ede!efdZ"edeed zdefdZ#e d&d!ed"edeed zdede$eee%ff d#Z&d S)'WPDisabledRulezStores disabled WordPress protection rules. Uses a scope-based design: - scope='global', scope_value=NULL: Rule disabled for all domains (root only) - scope='domain', scope_value='example.com': Rule disabled for specific domain c$eZdZejZdZdZdS)WPDisabledRule.Metawp_disabled_rules)))rule_idscope scope_valueTN)__name__ __module__ __qualname__r dbdatabasedb_tableindexes[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wp_disabled_rule.pyMetar-s;&@rrF)nullTglobaldomain wordpressagentNrdomainssourceuser_id timestampreturnc|tj}|r||||||S|||||S)a> Disable a rule globally or for specific domains. Args: rule_id: The rule identifier (e.g., "CVE-2025-001") domains: List of domains to disable for, or None/empty for global disable source: Origin of the action ("wordpress" or "agent") user_id: UID of the user performing the action (0 for root) timestamp: Unix timestamp for when the rule was disabled. If None, uses current time. Returns: Number of new entries created (0 if all were no-ops). )time_disable_for_domains_disable_globally)clsrr$r%r&r's rstorezWPDisabledRule.storeHs\.   I  ++)VW $$WiIIIrc|||jd|||}|rtd|||t |S)zADisable a rule globally (independent of domain-specific entries).Nrrr disabled_atr%r&z1Disabled rule %s globally (source=%s, user_id=%s))_create_if_not_exists SCOPE_GLOBALloggerdebugint)r-rr'r%r&createds rr,z WPDisabledRule._disable_globallyhsm++"! ,     LLC     7||rc d}|D]G}|||j||||}|r#|dz }td||||H|S)zBDisable a rule for specific domains (independent of global state).rr0z6Disabled rule %s for domain %s (source=%s, user_id=%s))r2 SCOPE_DOMAINr4r5) r-rr$r'r%r&countr!r7s rr+z#WPDisabledRule._disable_for_domainss  F//&"% 0G   L  rrrr1c |||||||dS#t$rYdSwxYw)z Create a new disabled rule entry if it doesn't already exist. Returns: True if a new entry was created, False if it already existed (no-op) )rrrr1r%created_by_user_idTF)insertexecuter)r-rrrr1r%r&s rr2z$WPDisabledRule._create_if_not_existssc  JJ''#*   giii4   55 s -1 ??c|so||j|k|j|jk}|rt d|n||j|k|j|jk|j |}|rt d|||S)a Re-enable a rule globally or for specific domains. Args: rule_id: The rule identifier domains: List of domains to enable for, or None/empty to enable globally Returns: Number of rows deleted zEnabled rule %s globallyz Enabled rule %s for %d domain(s)) deletewhererrr3r?r4r5r:rin_)r-rr$r;s rremovezWPDisabledRule.removes  K7*I!11  B 7AAA K7*I!11O''00    6  rc|Q||j|k|j|jkS||j|k|j|jk|j|jk|j|kzzS)a" Check if a rule is disabled globally or for a specific domain. Args: rule_id: The rule identifier domain: The domain to check. If None, only checks global disable. Returns: True if the rule is disabled, False otherwise )selectrBrrr3existsr:r)r-rr!s ris_rule_disabledzWPDisabledRule.is_rule_disableds > K7*I!11   JJLL U w&Y#"22c&66?f46  VXX rinclude_globalc~|rk||j|j|jk|j|jk|j|kzz}nE||j|j|jk|j|k}d|DS)a Get all rule IDs that are disabled for a specific domain. Args: domain: The domain to get disabled rules for include_global: If True, also include globally disabled rules. If False (default), only return domain-specific disables. Returns: List of rule IDs that are disabled for the domain cg|] }|j Srr.0rows r z6WPDisabledRule.get_domain_disabled../s--- ---r)rFrrBrr3r:rdistinct)r-r!rIquerys rget_domain_disabledz"WPDisabledRule.get_domain_disableds   3;''Y#"22c&66?f46 EJJs{++11 S--6)E.-u----rc||j|j|jk}d|DS)z Get all rule IDs that are disabled globally. Returns: Iterator of globally disabled rule IDs c3$K|] }|jV dSNrLrMs r z5WPDisabledRule.get_global_disabled..:s$-- ------r)rFrrBrr3)r-rRs rget_global_disabledz"WPDisabledRule.get_global_disabled1sC 3;''--ci3;K.KLL--u----r user_domainsc|B|j|jk|j|z}|r|j|jk|zS|S|s|j|jkSdS)z Build the WHERE condition for filtering rules. Returns: A Peewee expression for the WHERE clause, or None if no filter needed. N)rr:rrCr3)r-rYrI domain_matchs r_build_filter_conditionz&WPDisabledRule._build_filter_condition<st  #I)99##L11L F S%55EE  19 00 0trrlimitoffsetc|||}||j|jt j|j}|| |}| }d| | |D}|s|gfS| |j |} || |} i} | D]z} | j| vr| jdgd| | j<| j|jkrd| | jd<?| j|jkr+| | jd| j{g} |D]7} | | }t'|d|d<| |8|| fS)a> List disabled rules with aggregation by rule_id. Multiple domain entries for the same rule are aggregated into a single result with a list of domains. Results are ordered by most recently disabled first (using the latest disabled_at timestamp per rule_id). Uses a two-pass approach for efficiency: 1. First pass: Get rule_ids ordered by latest disabled_at with pagination 2. Second pass: Fetch only rows for the paginated rule_ids Args: limit: Maximum number of rules to return offset: Number of rules to skip user_domains: If provided, only return rules for these domains. If None, return all rules (for root users). include_global: Whether to include global rules in the result Returns: Tuple of (total_count, list of rule dicts) Each dict has: {"rule_id": str, "is_global": bool, "domains": list[str]} is_global is True if rule has a global disable, domains lists domain-specific disables Ncg|] }|j SrrLrMs rrPz(WPDisabledRule.fetch..s'   CK   rF)r is_globalr$Trar$)r\rFrgroup_byorder_byr MAXr1descrBr;r^r]rCrr3r:appendrsorted)r-r]r^rYrI conditionrule_ids_query total_countpaginated_rule_ids rows_query rules_by_idrOresultr rule_datas rfetchzWPDisabledRule.fetchSs@// nMM  JJs{ # # Xck " " XbfS_--2244 5 5   +11)<#?#?Ii MM) $ $ $ $F""rrV)F)rNF)'rrr__doc__rridrrrrrr1r%rr=r3r:SOURCE_WORDPRESS SOURCE_AGENT classmethodstrlistr6floatr.r,r+boolr2rDrHrSrrXr\tupledictrprrrr r %sAAAAAAAA   BiU###G I5 ! ! !E)&&&K*%(((K YE " " "F%5111LL#L#' JJJcT!J J  J 4< J JJJ[J>    [2c      [<4Z      [:(S(49t+;(((([(T" " s" C$J" $" " " [" H16...*.. c...[.B.HSM...[.3i$&[,)-$ O#O#O#O#3i$& O#  O# sDJ  O#O#O#[O#O#O#rr incidentsr(c`d|D}|s |D]}d|d<dSd|D}tjtjk}|r=|tjtjktj|zz}ttjtjtjtj||}t}t}|D]S}|jtjkr| |j2| |j|jfT|D]N}| d} | d} t| duo | |vp | duo| | f|v|d<OdS)aSet is_rule_disabled on each incident dict in place. A rule is considered disabled for an incident when wp_disabled_rules has a row with rule_id == incident["rule"] AND (scope='global' OR (scope='domain' AND scope_value == incident["domain"])). Incidents with a NULL rule (legacy/imported rows) always get False. Runs at most one SELECT regardless of the input length. cHh|]}|d|d S)rulegetrNincs r z7enrich_incidents_with_disabled_state..s2CGGFOO,GF ,G,G,GrFrHNcHh|]}|d|d S)r!rrs rrz7enrich_incidents_with_disabled_state..s4cggh.?.?.KH .K.K.Krrr!) r rr3r:rrCrFrrBsetaddrry) r|rule_idsrr$rhrRglobally_disableddomain_disabledrOrr!s r$enrich_incidents_with_disabled_staters(H  , ,C&+C" # #!*G$(CCI   !^%@ @)--g66 8   ! !"   eN " & &x 0 0)<<  #&%%,/EEO@@ 93 3 3  ! !#+ . . . .   co > ? ? ? ?   wwv"""&   ))N$&LD&>_+L # #     r)rqcollections.abcrloggingr*peeweerrrrrr defence360agent.modelr r getLoggerrr4r rwr{rrrrrs$%$$$$$ 21111111  8 $ $~#~#~#~#~#U~#~#~#B 3 DJ3 43 3 3 3 3 3 rdefence360agent/model/__pycache__/wp_disabled_rule.cpython-311.pyc0000644000000000000000000004335200000000000022073 0ustar r_j<dZddlmZddlZddlZddlmZmZmZm Z m Z m Z ddl m Z mZejeZGdde Zdeed dfd ZdS) aWordPress-specific disabled rules data model. This module provides a separate data model for WordPress disabled rules, independent of the existing DisabledRule/DisabledRuleDomain models used by modsec/ossec plugins. Disable Behavior: Global and domain-level disables are independent and can coexist. A rule is considered effectively disabled for a given WordPress domain if EITHER of these conditions is true: - A global disable exists for the rule (applies to all domains) - A domain-specific disable exists for the rule and that domain Enabling a rule at one scope does not affect disables at the other scope. For example, removing a global disable leaves any domain-specific disables intact, and vice versa. )IteratorN) CharField FloatField IntegerFieldIntegrityErrorPrimaryKeyFieldfn)Modelinstancec eZdZdZGddZeZedZedZ edZ e dZ edZ edZdZdZd Zd Ze d$d ed eed zdededed zdef dZed ededededef dZed ed eededededef dZed ededed zdedededefdZed ed eed zdefdZed$d eded zdefdZe d%dededeefdZ ede!efdZ"edeed zdefdZ#e d&d!ed"edeed zdede$eee%ff d#Z&d S)'WPDisabledRulezStores disabled WordPress protection rules. Uses a scope-based design: - scope='global', scope_value=NULL: Rule disabled for all domains (root only) - scope='domain', scope_value='example.com': Rule disabled for specific domain c$eZdZejZdZdZdS)WPDisabledRule.Metawp_disabled_rules)))rule_idscope scope_valueTN)__name__ __module__ __qualname__r dbdatabasedb_tableindexes[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/model/wp_disabled_rule.pyMetar-s;&@rrF)nullTglobaldomain wordpressagentNrdomainssourceuser_id timestampreturnc|tj}|r||||||S|||||S)a> Disable a rule globally or for specific domains. Args: rule_id: The rule identifier (e.g., "CVE-2025-001") domains: List of domains to disable for, or None/empty for global disable source: Origin of the action ("wordpress" or "agent") user_id: UID of the user performing the action (0 for root) timestamp: Unix timestamp for when the rule was disabled. If None, uses current time. Returns: Number of new entries created (0 if all were no-ops). )time_disable_for_domains_disable_globally)clsrr$r%r&r's rstorezWPDisabledRule.storeHs\.   I  ++)VW $$WiIIIrc|||jd|||}|rtd|||t |S)zADisable a rule globally (independent of domain-specific entries).Nrrr disabled_atr%r&z1Disabled rule %s globally (source=%s, user_id=%s))_create_if_not_exists SCOPE_GLOBALloggerdebugint)r-rr'r%r&createds rr,z WPDisabledRule._disable_globallyhsm++"! ,     LLC     7||rc d}|D]G}|||j||||}|r#|dz }td||||H|S)zBDisable a rule for specific domains (independent of global state).rr0z6Disabled rule %s for domain %s (source=%s, user_id=%s))r2 SCOPE_DOMAINr4r5) r-rr$r'r%r&countr!r7s rr+z#WPDisabledRule._disable_for_domainss  F//&"% 0G   L  rrrr1c |||||||dS#t$rYdSwxYw)z Create a new disabled rule entry if it doesn't already exist. Returns: True if a new entry was created, False if it already existed (no-op) )rrrr1r%created_by_user_idTF)insertexecuter)r-rrrr1r%r&s rr2z$WPDisabledRule._create_if_not_existssc  JJ''#*   giii4   55 s -1 ??c|so||j|k|j|jk}|rt d|n||j|k|j|jk|j |}|rt d|||S)a Re-enable a rule globally or for specific domains. Args: rule_id: The rule identifier domains: List of domains to enable for, or None/empty to enable globally Returns: Number of rows deleted zEnabled rule %s globallyz Enabled rule %s for %d domain(s)) deletewhererrr3r?r4r5r:rin_)r-rr$r;s rremovezWPDisabledRule.removes  K7*I!11  B 7AAA K7*I!11O''00    6  rc|Q||j|k|j|jkS||j|k|j|jk|j|jk|j|kzzS)a" Check if a rule is disabled globally or for a specific domain. Args: rule_id: The rule identifier domain: The domain to check. If None, only checks global disable. Returns: True if the rule is disabled, False otherwise )selectrBrrr3existsr:r)r-rr!s ris_rule_disabledzWPDisabledRule.is_rule_disableds > K7*I!11   JJLL U w&Y#"22c&66?f46  VXX rinclude_globalc~|rk||j|j|jk|j|jk|j|kzz}nE||j|j|jk|j|k}d|DS)a Get all rule IDs that are disabled for a specific domain. Args: domain: The domain to get disabled rules for include_global: If True, also include globally disabled rules. If False (default), only return domain-specific disables. Returns: List of rule IDs that are disabled for the domain cg|] }|j Srr.0rows r z6WPDisabledRule.get_domain_disabled../s--- ---r)rFrrBrr3r:rdistinct)r-r!rIquerys rget_domain_disabledz"WPDisabledRule.get_domain_disableds   3;''Y#"22c&66?f46 EJJs{++11 S--6)E.-u----rc||j|j|jk}d|DS)z Get all rule IDs that are disabled globally. Returns: Iterator of globally disabled rule IDs c3$K|] }|jV dSNrLrMs r z5WPDisabledRule.get_global_disabled..:s$-- ------r)rFrrBrr3)r-rRs rget_global_disabledz"WPDisabledRule.get_global_disabled1sC 3;''--ci3;K.KLL--u----r user_domainsc|B|j|jk|j|z}|r|j|jk|zS|S|s|j|jkSdS)z Build the WHERE condition for filtering rules. Returns: A Peewee expression for the WHERE clause, or None if no filter needed. N)rr:rrCr3)r-rYrI domain_matchs r_build_filter_conditionz&WPDisabledRule._build_filter_condition<st  #I)99##L11L F S%55EE  19 00 0trrlimitoffsetc|||}||j|jt j|j}|| |}| }d| | |D}|s|gfS| |j |} || |} i} | D]z} | j| vr| jdgd| | j<| j|jkrd| | jd<?| j|jkr+| | jd| j{g} |D]7} | | }t'|d|d<| |8|| fS)a> List disabled rules with aggregation by rule_id. Multiple domain entries for the same rule are aggregated into a single result with a list of domains. Results are ordered by most recently disabled first (using the latest disabled_at timestamp per rule_id). Uses a two-pass approach for efficiency: 1. First pass: Get rule_ids ordered by latest disabled_at with pagination 2. Second pass: Fetch only rows for the paginated rule_ids Args: limit: Maximum number of rules to return offset: Number of rules to skip user_domains: If provided, only return rules for these domains. If None, return all rules (for root users). include_global: Whether to include global rules in the result Returns: Tuple of (total_count, list of rule dicts) Each dict has: {"rule_id": str, "is_global": bool, "domains": list[str]} is_global is True if rule has a global disable, domains lists domain-specific disables Ncg|] }|j SrrLrMs rrPz(WPDisabledRule.fetch..s'   CK   rF)r is_globalr$Trar$)r\rFrgroup_byorder_byr MAXr1descrBr;r^r]rCrr3r:appendrsorted)r-r]r^rYrI conditionrule_ids_query total_countpaginated_rule_ids rows_query rules_by_idrOresultr rule_datas rfetchzWPDisabledRule.fetchSs@// nMM  JJs{ # # Xck " " XbfS_--2244 5 5   +11)<#?#?Ii MM) $ $ $ $F""rrV)F)rNF)'rrr__doc__rridrrrrrr1r%rr=r3r:SOURCE_WORDPRESS SOURCE_AGENT classmethodstrlistr6floatr.r,r+boolr2rDrHrSrrXr\tupledictrprrrr r %sAAAAAAAA   BiU###G I5 ! ! !E)&&&K*%(((K YE " " "F%5111LL#L#' JJJcT!J J  J 4< J JJJ[J>    [2c      [<4Z      [:(S(49t+;(((([(T" " s" C$J" $" " " [" H16...*.. c...[.B.HSM...[.3i$&[,)-$ O#O#O#O#3i$& O#  O# sDJ  O#O#O#[O#O#O#rr incidentsr(c`d|D}|s |D]}d|d<dSd|D}tjtjk}|r=|tjtjktj|zz}ttjtjtjtj||}t}t}|D]S}|jtjkr| |j2| |j|jfT|D]N}| d} | d} t| duo | |vp | duo| | f|v|d<OdS)aSet is_rule_disabled on each incident dict in place. A rule is considered disabled for an incident when wp_disabled_rules has a row with rule_id == incident["rule"] AND (scope='global' OR (scope='domain' AND scope_value == incident["domain"])). Incidents with a NULL rule (legacy/imported rows) always get False. Runs at most one SELECT regardless of the input length. cHh|]}|d|d S)rulegetrNincs r z7enrich_incidents_with_disabled_state..s2CGGFOO,GF ,G,G,GrFrHNcHh|]}|d|d S)r!rrs rrz7enrich_incidents_with_disabled_state..s4cggh.?.?.KH .K.K.Krrr!) r rr3r:rrCrFrrBsetaddrry) r|rule_idsrr$rhrRglobally_disableddomain_disabledrOrr!s r$enrich_incidents_with_disabled_staters(H  , ,C&+C" # #!*G$(CCI   !^%@ @)--g66 8   ! !"   eN " & &x 0 0)<<  #&%%,/EEO@@ 93 3 3  ! !#+ . . . .   co > ? ? ? ?   wwv"""&   ))N$&LD&>_+L # #     r)rqcollections.abcrloggingr*peeweerrrrrr defence360agent.modelr r getLoggerrr4r rwr{rrrrrs$%$$$$$ 21111111  8 $ $~#~#~#~#~#U~#~#~#B 3 DJ3 43 3 3 3 3 3 rdefence360agent/model/analyst_cleanup.py0000644000000000000000000000663300000000000015412 0ustar import peewee as pw from defence360agent.model import Model, instance from datetime import datetime, timezone, timedelta class AnalystCleanupRequest(Model): """ Model for storing analyst cleanup requests. Tracks request details and status for each cleanup request submitted. """ class Meta: database = instance.db db_table = "analyst_cleanup_requests" id = pw.AutoField() username = pw.CharField(null=False) zendesk_id = pw.CharField(null=False) ticket_link = pw.TextField(null=False) created_at = pw.TimestampField( null=False, default=datetime.now(timezone.utc) ) status = pw.CharField( null=False, default="pending", constraints=[ pw.Check("status in ('pending','in_progress','completed')") ], ) last_updated = pw.TimestampField( null=False, default=datetime.now(timezone.utc) ) @classmethod def create_request(cls, username, zendesk_id, ticket_link): """Create a new cleanup request""" return cls.create( username=username, zendesk_id=zendesk_id, ticket_link=ticket_link ) @classmethod def get_user_requests(cls, username, limit=50, offset=0): """Get all requests for a specific user""" return ( cls.select() .where(cls.username == username) .order_by(cls.created_at.desc()) .limit(limit) .offset(offset) ) @classmethod def get_all_requests(cls, limit=50, offset=0): """Get all requests for a sever""" return ( cls.select() .order_by(cls.created_at.desc()) .limit(limit) .offset(offset) ) @classmethod def get_active_request_link(cls, username) -> str | None: """ Gets user requests for a user and checks if there are requests with [pending | in_progress] state. If found, returns ticket_link, otherwise returns None """ active_request = ( cls.select() .where( (cls.username == username) & (cls.status.in_(["pending", "in_progress"])) ) .limit(1) ).first() return active_request.ticket_link if active_request else None @classmethod def update_status(cls, zendesk_id, new_status, last_updated): """Update the status of a request""" return ( cls.update(status=new_status, last_updated=last_updated) .where(cls.zendesk_id == zendesk_id) .execute() ) @classmethod def get_all_relevant_requests(cls): """ Returns a query to fetch active cleanup requests and recently completed requests for the specified users. """ # Calculate the cutoff date for "recently completed" (3 days ago) three_days_ago = datetime.now(timezone.utc) - timedelta(days=3) return AnalystCleanupRequest.select( AnalystCleanupRequest.username, AnalystCleanupRequest.zendesk_id, AnalystCleanupRequest.status, AnalystCleanupRequest.last_updated, ).where( (AnalystCleanupRequest.status.in_(["pending", "in_progress"])) | ( (AnalystCleanupRequest.status == "completed") & (AnalystCleanupRequest.last_updated >= three_days_ago) ) ) defence360agent/model/event_hook.py0000644000000000000000000000336700000000000014372 0ustar from time import time from peewee import CharField, IntegerField, BooleanField from defence360agent.model import instance, Model from defence360agent.model.simplification import FilenameField class EventHook(Model): """Imunify Hooks v1.0 configuration. .. deprecated:: 4.10 A new notification system was implemented in DEF-11680 """ class Meta: database = instance.db db_table = "event_hook" #: The path to the hook script. path = FilenameField(null=False) #: The event for which it should trigger. event = CharField(null=False) #: Timestamp when the hook was added. created = IntegerField(null=False, default=lambda: int(time())) #: Native hooks can be imported and executed as Python directly, without #: creating a separate process. native = BooleanField(default=False) @classmethod def list_events(cls, event): q = cls.select() if event != "all": q = q.where(cls.event == event) return list(q.dicts()) @classmethod def add_hook(cls, event, path, native=False): q = cls.select().where((cls.event == event) & (cls.path == path)) if q.exists(): return None hook = cls.create(event=event, path=path, native=native) return hook.as_dict() @classmethod def delete_hook(cls, event, path): q = cls.select().where((cls.event == event) & (cls.path == path)) if not q.exists(): return None hook = q.get() data = hook.as_dict() hook.delete_instance() return data def as_dict(self): return { "path": self.path, "event": self.event, "created": self.created, "native": self.native, } defence360agent/model/icontact.py0000644000000000000000000000223700000000000014030 0ustar import time from peewee import CharField, IntegerField, CompositeKey from defence360agent.contracts.config import IContactMessageType from defence360agent.model import Model, instance from defence360agent.utils.common import DAY, WEEK THROTTLING_PERIOD = { IContactMessageType.MALWARE_FOUND: DAY, IContactMessageType.SCAN_NOT_SCHEDULED: WEEK, } class IContactThrottle(Model): class Meta: database = instance.db db_table = "icontact_throttle" primary_key = CompositeKey("message_type", "user") message_type = CharField() user = CharField(null=True) #: The last time we sent a notification about :attr:`message_type` timestamp = IntegerField(default=0) @classmethod def may_be_notified(cls, message_type, period_limit, user=None): obj, _ = cls.get_or_create(message_type=message_type, user=user) return (time.time() - obj.timestamp) > period_limit @classmethod def refresh(cls, message_type, user=None): cls.update(timestamp=time.time()).where( cls.message_type == message_type, cls.user.is_null(True) if user is None else cls.user == user, ).execute() defence360agent/model/infected_domain.py0000644000000000000000000001036200000000000015332 0ustar import itertools import logging import time from peewee import ( CharField, FloatField, IntegerField, TextField, ) from defence360agent.model import instance, Model logger = logging.getLogger(__name__) class InfectedDomainList(Model): """Domains with bad reputation, used for Reputation Management feature.""" id = IntegerField(primary_key=True) #: Username associated with the domain in hosting panel. username = CharField(null=True) #: Domain name. name = CharField(null=False) #: The kind of threat reported by reputation engine, #: e.g. "SOCIAL_ENGINEERING". threat_type = CharField(null=False) #: The time when Imunify first detected that the domain has bad reputation. timestamp = FloatField() #: The name of the reputation engine, e.g. "google-safe-browsing". vendor = TextField(null=True) class Meta: database = instance.db db_table = "infected_domain_list" @classmethod def get_by_user(cls, existing_users, offset=0, limit=50): # to be able to filter query results using existing_users, # limit/offset os applied on python side query = cls.select().order_by( cls.username, cls.name, cls.timestamp.desc() ) filtered_by_user = ( row for row in query.dicts() if row["username"] in existing_users ) grouped = itertools.groupby( filtered_by_user, key=lambda row: (row["username"], row["name"]) ) max_count = 0 result = [] for i, value in enumerate(grouped): max_count += 1 if (len(result) < limit) and (i >= offset): group, threats = value username, name = group result.append( { "username": username, "domain": name, "threats": [ { "type": t["threat_type"], "vendor": t["vendor"], "timestamp": t["timestamp"], } for t in threats ], } ) return result, max_count @classmethod def refresh_domains(cls, domains, domains_to_users): """ Update domain reputatuion info. If threat info already exists, do not update timestamp :param domains: reputation data from server :param domains_to_users: domain -> users mapping from hosting panel :return: """ existing = { (r["name"], r["threat_type"], r["vendor"]): r["timestamp"] for r in cls.select().dicts() } with instance.db.atomic(): cls.delete().execute() now = time.time() for domain_info in domains: domain = domain_info["query"] if domain not in domains_to_users: logger.warning("Users for domain %s not found.", domain) continue for user in domains_to_users[domain]: vendor = domain_info["vendor"] if vendor in ( "google-safe-browsing", "yandex-safe-browsing", ): threat_type = domain_info["details"]["threat_type"] elif vendor == "spamhaus": threat_type = domain_info["details"] elif vendor in ("phishtank", "openphish"): # https://cloudlinux.atlassian.net/wiki/spaces/IPT/pages/929759302/4.1+Multiple+vendors+in+Reputation+Management+ver.4.2 # noqa: E501 threat_type = "spam domain" else: threat_type = "THREAT_TYPE_UNSPECIFIED" timestamp = existing.get( (domain, threat_type, vendor), now ) cls.create( username=user, name=domain, threat_type=threat_type, vendor=vendor, timestamp=timestamp, ) defence360agent/model/instance.py0000644000000000000000000000103100000000000014017 0ustar import defence360agent.model.tls_check as tls_check # actual database connection is done during runtime, to prevent # 'locking protocol' error when bringing database connection though # fork() (during demonization) # See https://stackoverflow.com/questions/46331178/causes-of-sqlite3-operationalerror-locking-protocol-exception # noqa E501 db = tls_check.SqliteDatabaseWrapper( None, pragmas=[ ("journal_mode", "wal"), ("foreign_keys", "ON"), ("busy_timeout", 10000), ], regexp_function=True, ) defence360agent/model/messages_to_send.py0000644000000000000000000000310300000000000015537 0ustar from collections import namedtuple from peewee import FloatField, BlobField from defence360agent.model import instance, Model class MessageToSend(Model): """ Storage for messages to be sent to server while connection to server is not available """ class Meta: database = instance.db db_table = "messages_to_send_nr" #: When the message was added to the queue to be sent to the server. timestamp = FloatField(null=False) #: The message itself. message = BlobField(null=False) MessageToSendT = namedtuple("MessageToSendT", "timestamp message") @classmethod def get_all_ordered(cls): return cls.select(cls.id, cls.timestamp, cls.message).order_by( cls.timestamp, cls.id ) @classmethod def set_message(cls, message_id, message): return ( cls.update(message=message).where(cls.id == message_id).execute() ) @classmethod def delete_in(cls, query): q = cls.delete().where(cls.id.in_(query)) return q.execute() @classmethod def delete_old(cls, limit=1): old = cls.select().order_by(cls.timestamp).limit(limit) q = cls.delete().where(cls.id.in_(old)) return q.execute() @classmethod def insert_many(cls, rows, **kwargs) -> None: # sqlite may have internal limit of variables-per-query for i in range(0, len(rows), 100): data = [ cls.MessageToSendT(*row)._asdict() for row in rows[i : i + 100] ] super().insert_many(data, **kwargs).execute() defence360agent/model/simplification.py0000644000000000000000000001201500000000000015231 0ustar import inspect import logging import os import time from peewee import ( BlobField, CharField, DateField, ForeignKeyField, IntegerField, PeeweeException, ) from defence360agent.model import instance, Model #: seconds in a POSIX day POSIX_DAY = 24 * 60 * 60 logger = logging.getLogger(__name__) class FilenameField(BlobField): """ Class to store file names in database """ def db_value(self, value): return os.fsencode(value) def python_value(self, value): return os.fsdecode(value) class ScanPathField(CharField): REALTIME_SCAN_PATH_STUB = "list_of_files" def db_value(self, value): if isinstance(value, list): return self.REALTIME_SCAN_PATH_STUB return value class ModelError(PeeweeException): """ Model exception. Please use this one from other modules instead PeeweeException directly """ pass async def run_in_executor(loop, cb, *args): """ Fake run_in_executor() test (DEF-4541) """ return cb(*args) def remove_old_and_truncate( table: Model, num_days: int, max_count: int ) -> int: """ Removes records that is older that *num_days* days and all others that are out of range *max_count* from *table*. Returns count of rows deleted. """ has_timestamp = getattr(table, "timestamp", False) if not has_timestamp: raise ValueError("No 'timestamp' column in table {!r}".format(table)) # keep no more than *max_count* rows that are newer than *num_days* end_save_time = time.time() - num_days * POSIX_DAY to_keep = ( table.select(table.timestamp) .order_by(table.timestamp.desc()) .limit(max_count) .where(table.timestamp > end_save_time) ) deleted_count = ( table.delete().where(table.timestamp.not_in(to_keep)).execute() ) return deleted_count class Eula(Model): """Keeps track of updates and acceptions of end user license agreement. Admins will be asked to accept EULA if the latest version is not accepted yet. """ class Meta: database = instance.db db_table = "eula" #: Date when EULA was updated. updated = DateField(primary_key=True) #: Timestamp when EULA was accepted. accepted = IntegerField(null=True, default=None) @classmethod def is_accepted(cls) -> bool: unaccepted = next( iter( cls.select() .where(cls.accepted.is_null()) .order_by(cls.updated) .limit(1) ), None, ) return unaccepted is None @classmethod def accept(cls) -> None: cls.update(accepted=time.time()).where( cls.accepted.is_null() ).execute() def get_models(module): return [ obj for _, obj in inspect.getmembers( module, lambda obj: inspect.isclass(obj) and issubclass(obj, Model) and obj != Model, ) ] def create_tables(module): instance.db.connect() instance.db.create_tables(get_models(module), safe=True) class ApplyOrderBy: @staticmethod def resolve_nodes(_model, column_name: str) -> tuple: """ :param _model: peewee.Model or peewee.ForeignKeyField :param column_name: str :return: tuple """ model = ( _model.rel_model if isinstance(_model, ForeignKeyField) else _model ) nodes = () custom_order_by = getattr(model, "OrderBy", None) if custom_order_by is not None: nodes = getattr(custom_order_by, column_name, lambda: nodes)() if not nodes: node = getattr(model, column_name, None) if node is not None: nodes = (node,) # type: ignore return nodes @staticmethod def get_nodes(model, column_names: list) -> list: """ :param model: peewee.Model or peewee.ForeignKeyField :param column_names: list :return: list """ column_name, rest = column_names[0], column_names[1:] nodes = ApplyOrderBy.resolve_nodes(model, column_name) result = [] for node_or_model in nodes: if rest: # model for node in ApplyOrderBy.get_nodes(node_or_model, rest): result.append(node) else: # node result.append(node_or_model) return result def __call__(self, order_by, model, query_builder): """ :param order_by: list :param model: peewee.Model or peewee.ForeignKeyField :param query_builder: peewee.Query :return: peewee.Query with applied order_by """ orders = [] for order in order_by: nodes = ApplyOrderBy.get_nodes(model, order.column_name.split(".")) for node in nodes: orders.append(node.desc() if order.desc else node) return query_builder.order_by(*orders) apply_order_by = ApplyOrderBy() defence360agent/model/tls_check.py0000644000000000000000000000435400000000000014165 0ustar import logging import threading import time import traceback from playhouse.sqlite_ext import SqliteExtDatabase from defence360agent.internals.global_scope import g class OverridingReset(Exception): """ Overriding reset could be a signal of logic error thus need to be explicitly handled in all places where this exception is expected to occur. """ pass logger = logging.getLogger(__name__) _thread_local_storage = threading.local() _SLOW_TXN_THRESHOLD_S = 5.0 class _TimedAtomic: def __init__(self, inner: object): self._inner = inner self._start: float = 0.0 self._caller: str = "" def __enter__(self): self._start = time.monotonic() self._caller = "".join(traceback.format_stack(limit=4)[:-1]) return self._inner.__enter__() def __exit__(self, *args): result = self._inner.__exit__(*args) elapsed = time.monotonic() - self._start if elapsed > _SLOW_TXN_THRESHOLD_S: logger.warning( "Slow transaction held for %.2fs\n%s", elapsed, self._caller, ) return result class SqliteDatabaseWrapper(SqliteExtDatabase): def execute_sql(self, *args, **kwargs): _validate(*args, **kwargs) return super().execute_sql(*args, **kwargs) def atomic(self, lock_type: str = "IMMEDIATE"): inner = super().atomic(lock_type) if g.get("DEBUG"): return _TimedAtomic(inner) return inner def reset(new_value=None): if hasattr(_thread_local_storage, "thread_ident_memo"): raise OverridingReset() _thread_local_storage.thread_ident_memo = ( new_value or threading.get_ident() ) def _validate(*args, **kwargs): thread_ident_memo = getattr( _thread_local_storage, "thread_ident_memo", None ) if thread_ident_memo is None: logger.error("wrong thread or _validate() was not preceded by reset()") elif thread_ident_memo != threading.get_ident(): logger.error( "thread_ident_memo check failed [%r != %r]\n" "context:\nargs: %s\nkwargs: %s", thread_ident_memo, threading.get_ident(), args, kwargs, ) defence360agent/model/wordpress.py0000644000000000000000000000324700000000000014256 0ustar from __future__ import annotations from typing import NamedTuple from peewee import CharField, FloatField, IntegerField, TimestampField from defence360agent.model import instance, Model class WPSite(NamedTuple): docroot: str domain: str uid: int version: str = "1.0.0" @classmethod def from_wordpress_site(cls, site: WordpressSite) -> WPSite: """Create a WPSite instance from a WordpressSite instance.""" return cls( docroot=site.docroot, domain=site.domain, uid=site.uid, version=site.version, ) def build_with_version(self, version: str) -> WPSite: """Create a new WPSite instance with an updated version.""" return WPSite( docroot=self.docroot, domain=self.domain, uid=self.uid, version=version, ) def __eq__(self, other): if not isinstance(other, WPSite): return NotImplemented # Ignore version and manually_deleted_at for equality check. return (self.docroot, self.domain, self.uid) == ( other.docroot, other.domain, other.uid, ) def __hash__(self): return hash((self.docroot, self.domain, self.uid)) class WordpressSite(Model): class Meta: database = instance.db db_table = "wordpress_site" docroot = CharField(primary_key=True, null=False) domain = CharField(null=False) uid = IntegerField(null=False) manually_deleted_at = TimestampField(default=None, null=True) version = CharField(default="1.0.0", null=False) disabled_rules_sync_ts = FloatField(null=True, default=None) defence360agent/model/wordpress_incident.py0000644000000000000000000004673200000000000016141 0ustar """Helper functions for WordPress CVE protection incidents. WordPress incidents are stored in a dedicated wordpress_incident table with plugin-specific data stored in the extra_info JSON field. This module provides helper functions to work with WordPress incidents. Available for both AV and IM360 modes. """ import logging import time import json from collections import defaultdict from contextlib import ExitStack, contextmanager from datetime import timedelta from typing import Iterable, Mapping from peewee import ( EXCLUDED, SQL, CharField, FloatField, IntegerField, TextField, fn as peewee_fn, ) from playhouse.sqlite_ext import JSONField, fn from defence360agent.internals import geo from defence360agent.model import Model, instance from defence360agent.model.simplification import apply_order_by from defence360agent.rpc_tools.validate import OrderBy from defence360agent.utils import CHUNK_SIZE_SQL_QUERY, split_for_chunk logger = logging.getLogger(__name__) _UNSET = object() #: Width of an aggregation window, matching the resident agent's flush interval. BUCKET_SECONDS = 60 AGGREGATE_KEY = ( "abuser", "name", "plugin", "rule", "severity", "domain", "bucket", ) INSERT_CHUNK_SIZE = 50 #: Upper bound on stored rows, mirroring the resident agent's incident table. MAX_STORED_INCIDENTS = 100_000 class WordpressIncident(Model): """ WordPress incident model for CVE protection. Uses dedicated wordpress_incident table created in migration 191. Repeats of the same attack are aggregated per minute: the unique constraint on (abuser, name, plugin, rule, severity, domain, bucket) keeps one row per aggregation window, counted by retries. """ id = IntegerField(primary_key=True, null=True) plugin = CharField(null=True) rule = CharField(null=True) timestamp = FloatField(null=True) retries = IntegerField(null=True) severity = IntegerField(null=True) name = CharField(null=True) description = TextField(null=True) abuser = CharField(null=True) country = CharField(null=True, column_name="country_id") domain = TextField(null=True, default=None) extra_info = JSONField(null=True) bucket = IntegerField(null=True) # occurrences correlation has not acknowledged yet; the periodic task # re-sends the row until it reaches zero. A counter rather than a flag so # that occurrences merged into an already-reported row are still reported. # The DEFAULT is in the schema, not just in peewee, because # src/rpm-tests/test_wordpress/test_list_incidents.py inserts rows with # raw SQL that names its columns explicitly. unsent_retries = IntegerField( null=False, default=0, index=True, constraints=[SQL("DEFAULT 0")], ) class Meta: database = instance.db db_table = "wordpress_incident" indexes = ((AGGREGATE_KEY, True),) def build_extra_info(incident_data: dict, site_info: dict) -> dict: """ Build extra_info dict from incident data and site information. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username, user_id) Returns: Dict with all WordPress-specific fields for extra_info JSON column """ # Serialize JSON fields files_json = serialize_json_field(incident_data.get("FILES")) get_names_json = serialize_json_field(incident_data.get("GET_NAMES")) post_names_json = serialize_json_field(incident_data.get("POST_NAMES")) return { # WordPress plugin-populated fields "cve": incident_data.get("cve"), "mode": incident_data.get("mode"), "target": incident_data.get("target"), "slug": incident_data.get("slug"), "version": incident_data.get("version"), "user_logged_in": incident_data.get("user_logged_in"), "username": site_info.get("username"), "user_id": site_info.get("user_id"), "site_path": site_info.get("site_path"), # HTTP request details "request_method": incident_data.get("REQUEST_METHOD"), "script_filename": incident_data.get("SCRIPT_FILENAME"), "php_self": incident_data.get("PHP_SELF"), "path_info": incident_data.get("PATH_INFO"), "request_uri": incident_data.get("REQUEST_URI"), "query_string": incident_data.get("QUERY_STRING"), "http_x_forwarded_for": incident_data.get("HTTP_X_FORWARDED_FOR"), "http_user_agent": incident_data.get("HTTP_USER_AGENT"), "http_referer": incident_data.get("HTTP_REFERER"), # Request data "files": files_json, "get_names": get_names_json, "post_names": post_names_json, "raw_data": incident_data.get("RAW_DATA"), } def build_incident_dict( incident_data: dict, site_info: dict, geo_reader=_UNSET ) -> dict: """ Build complete incident dict ready for database insertion. This is used for both single incident creation and bulk insertion. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username, user_id) geo_reader: An open geo Reader (or None) to resolve the abuser country. Pass one from country_reader() when building many incidents in a loop to avoid reopening the mmdb per incident. When omitted, a short-lived reader is opened for this single call. Returns: Dict with all fields ready for Incident.create() or bulk insert """ message = incident_data.get("message") or build_message_fallback( incident_data ) extra_info = build_extra_info(incident_data, site_info) abuser_ip = incident_data.get("REMOTE_ADDR") or incident_data.get( "attacker_ip" ) if geo_reader is _UNSET: with country_reader() as reader: country = _country_code(reader, abuser_ip) else: country = _country_code(geo_reader, abuser_ip) timestamp = float(incident_data.get("ts", 0)) return { # Standard incident fields "plugin": "wordpress", "rule": incident_data.get("rule_id", "unknown"), "timestamp": timestamp, "bucket": int(timestamp // BUCKET_SECONDS), "retries": 1, "severity": calculate_severity(incident_data.get("mode")), "name": f"WordPress CVE: {incident_data.get('cve', 'Unknown')}", "description": message, "abuser": abuser_ip, "country": country, "domain": site_info.get("domain"), # JSONField automatically handles serialization - just pass the dict "extra_info": extra_info, } @contextmanager def country_reader(): """Yield an open geo Reader, or None when the mmdb can't be opened. Lets bulk callers open the mmap'd reader once instead of per incident, while keeping enrichment non-blocking when the geo bundle is missing. """ with ExitStack() as stack: try: reader = stack.enter_context(geo.reader()) except Exception as exc: logger.debug("GeoIP reader unavailable: %s", exc) yield None return yield reader def _country_code(reader, ip: str | None) -> str | None: if reader is None or not ip: return None try: return reader.get_code(ip) except Exception as exc: logger.debug("GeoIP lookup failed for %s: %s", ip, exc) return None def create_wordpress_incident( incident_data: dict, site_info: dict ) -> WordpressIncident: """ Create a WordPress incident in the wordpress_incident table. Args: incident_data: Dict with incident fields from PHP incident file site_info: Dict with site information (domain, site_path, username) Returns: WordpressIncident instance with WordPress fields populated in extra_info """ incident_dict = build_incident_dict(incident_data, site_info) return WordpressIncident.create(**incident_dict) def aggregate_incident_dicts(incident_dicts: list[dict]) -> list[dict]: """Collapse incidents sharing an aggregate key into one counted row.""" # The window comes from the incident's own timestamp, so a backlogged # file yields the same rows as live collection. groups: dict[tuple, dict] = {} for incident_dict in incident_dicts: key = tuple(incident_dict[field] for field in AGGREGATE_KEY) group = groups.get(key) if group is None: groups[key] = dict(incident_dict) continue group["retries"] += incident_dict["retries"] group["timestamp"] = min( group["timestamp"], incident_dict["timestamp"] ) return list(groups.values()) def wordpress_incident_to_dict(incident: WordpressIncident) -> dict: """ Convert a WordpressIncident model instance to a dictionary. Args: incident: WordpressIncident model instance Returns: Dictionary representation of the incident """ return { "id": incident.id, "plugin": incident.plugin, "rule": incident.rule, "timestamp": incident.timestamp, "retries": incident.retries, "severity": incident.severity, "name": incident.name, "description": incident.description, "abuser": incident.abuser, "country": incident.country, "domain": incident.domain, "extra_info": incident.extra_info, } def get_wordpress_incidents( limit: int = 1000, offset: int = 0, user_id: int | None = None, by_abuser_ip: str | None = None, by_country_code: str | None = None, by_domain: str | None = None, search: str | None = None, site_search: str | None = None, since: int | None = None, to: int | None = None, order_by: list | None = None, include_hidden: bool = False, ): """ Get WordPress incidents as dictionaries. Args: limit: Maximum number of incidents to return offset: Offset for pagination user_id: Filter by user ID (None = all) by_abuser_ip: Filter by abuser IP address (None = all) by_country_code: Filter by country code (None = all) by_domain: Filter by domain (None = all) search: Search in IP address, name, description, or domain (None = all) site_search: Filter by site path in extra_info (None = all) since: Filter by timestamp >= this value (unix timestamp, None = all) to: Filter by timestamp <= this value (unix timestamp, None = all) order_by: List of fields to order by (None = default order by timestamp desc). Can be either strings (e.g., ["timestamp+", "severity-"]) or OrderBy objects. Strings are automatically converted. include_hidden: When False (default), exclude incidents whose rule has the TEST- prefix (internal probe rules that the WordPress plugin hides from its admin UI). Returns: List of incident dictionaries """ query = WordpressIncident.select(WordpressIncident).where( (WordpressIncident.plugin == "wordpress") ) if not include_hidden: query = query.where( WordpressIncident.rule.is_null() | ~WordpressIncident.rule.startswith("TEST-") ) if user_id is not None: query = query.where( fn.json_extract(WordpressIncident.extra_info, "$.user_id") == user_id ) if by_abuser_ip is not None: query = query.where(WordpressIncident.abuser.contains(by_abuser_ip)) if by_country_code is not None: query = query.where(WordpressIncident.country == by_country_code) if by_domain is not None: query = query.where(WordpressIncident.domain.contains(by_domain)) if search is not None: query = query.where( WordpressIncident.name.contains(search) | WordpressIncident.description.contains(search) | WordpressIncident.domain.contains(search) | WordpressIncident.abuser.contains(search) ) if site_search is not None: query = query.where( fn.json_extract(WordpressIncident.extra_info, "$.site_path") == site_search ) if since is not None: query = query.where(WordpressIncident.timestamp.cast("REAL") >= since) if to is not None: query = query.where(WordpressIncident.timestamp.cast("REAL") <= to) # Apply ordering if order_by is not None: # Convert string format to OrderBy objects if needed converted_order_by = [] for item in order_by: if isinstance(item, str): converted_order_by.append(OrderBy.fromstring(item)) else: converted_order_by.append(item) query = apply_order_by(converted_order_by, WordpressIncident, query) else: # Default order by timestamp descending query = query.order_by(WordpressIncident.timestamp.desc()) query = query.limit(limit) query = query.offset(offset) return [wordpress_incident_to_dict(inc) for inc in query.execute()] def bulk_create_wordpress_incidents(incidents_data: list[dict]) -> int: """Store aggregated incidents, merging repeats into the stored row.""" if not incidents_data: return 0 conflict_target = [ getattr(WordpressIncident, field) for field in AGGREGATE_KEY ] # every occurrence a fresh row carries is still unreported rows = [ {**incident, "unsent_retries": incident.get("retries") or 1} for incident in incidents_data ] # Every chunk shares one transaction, so a failure leaves the table # untouched and the batch can be retried without double counting. with WordpressIncident._meta.database.atomic(): for start in range(0, len(rows), INSERT_CHUNK_SIZE): chunk = rows[start : start + INSERT_CHUNK_SIZE] WordpressIncident.insert_many(chunk).on_conflict( conflict_target=conflict_target, update={ WordpressIncident.retries: ( WordpressIncident.retries + EXCLUDED.retries ), # occurrences merged into an already-reported row are # unreported again, which is what keeps them from being # swallowed by a row correlation already acknowledged WordpressIncident.unsent_retries: ( WordpressIncident.unsent_retries + EXCLUDED.retries ), WordpressIncident.timestamp: peewee_fn.MIN( WordpressIncident.timestamp, EXCLUDED.timestamp ), }, ).execute() return len(incidents_data) def get_unsent_wordpress_incidents( limit: int, exclude_ids: Iterable[int] = (), ) -> list[dict]: """Get incidents carrying occurrences correlation has not acknowledged yet, oldest first. Unlike get_wordpress_incidents() this keeps TEST- rules: they are hidden from the WordPress admin UI but still belong in correlation. """ exclude_ids = set(exclude_ids) # in-flight rows are skipped in python rather than with a NOT IN: the set # grows with every unacknowledged batch, and binding thousands of ids per # cycle costs more than over-fetching by its size rows = ( WordpressIncident.select(WordpressIncident) .where( (WordpressIncident.plugin == "wordpress") & (WordpressIncident.unsent_retries > 0) ) .order_by( WordpressIncident.timestamp.asc(), WordpressIncident.id.asc() ) .limit(limit + len(exclude_ids)) ) incidents = [] for row in rows: if row.id in exclude_ids: continue # attached here rather than in wordpress_incident_to_dict: the UI # reads that shape too and has no use for delivery bookkeeping incidents.append( { **wordpress_incident_to_dict(row), "unsent_retries": row.unsent_retries, } ) if len(incidents) == limit: break return incidents def settle_wordpress_incidents_reported(reported: Mapping[int, int]) -> int: """Discount the occurrences correlation acknowledged. Subtracts instead of clearing so that occurrences merged into a row while its message was in flight stay pending rather than being dropped. """ by_amount = defaultdict(list) for incident_id, amount in reported.items(): if amount > 0: by_amount[amount].append(incident_id) settled = 0 # chunked because sqlite caps the variables one query may bind, and the # acknowledged set is only bounded by how large a batch the sender built with WordpressIncident._meta.database.atomic(): for amount, incident_ids in by_amount.items(): for chunk in split_for_chunk( incident_ids, chunk_size=CHUNK_SIZE_SQL_QUERY ): settled += ( WordpressIncident.update( unsent_retries=fn.MAX( 0, WordpressIncident.unsent_retries - amount ) ) .where(WordpressIncident.id.in_(chunk)) .execute() ) return settled def delete_old_wordpress_incidents( days: int, limit: int | None = MAX_STORED_INCIDENTS ): cutoff_time = time.time() - timedelta(days=days).total_seconds() is_wordpress = WordpressIncident.plugin == "wordpress" stale = WordpressIncident.timestamp.cast("REAL") < cutoff_time # Probing for the oldest row worth keeping costs an indexed lookup; the # keep-set below matches every row against it, so only run that when the # probe says the table is actually over the cap. over_cap = ( limit and ( WordpressIncident.select(WordpressIncident.timestamp) .order_by(WordpressIncident.timestamp.desc()) .limit(1) .offset(limit) .scalar() ) is not None ) if over_cap: keep = ( WordpressIncident.select(WordpressIncident.id) .where(~stale) .order_by(WordpressIncident.timestamp.desc()) .limit(limit) ) stale |= WordpressIncident.id.not_in(keep) return WordpressIncident.delete().where(is_wordpress & stale).execute() def build_message_fallback(incident_data: dict) -> str: """Build message if plugin didn't provide one (per spec format).""" parts = ["IM WP plugin:"] if incident_data.get("rule_id"): parts.append(incident_data["rule_id"]) if incident_data.get("cve"): parts.append(incident_data["cve"]) if incident_data.get("slug"): parts.append(incident_data["slug"]) if incident_data.get("version"): parts.append(incident_data["version"]) if incident_data.get("mode"): parts.append(incident_data["mode"]) return " ".join(parts) def calculate_severity(mode: str | None) -> int: """Calculate severity based on mode.""" if mode == "block": return 8 # Higher severity for blocked attacks elif mode == "pass": return 5 # Medium severity for monitored attacks else: return 5 # Default def serialize_json_field(value) -> str | None: """Serialize a value to JSON string if it's not already a string.""" if value is None: return None if isinstance(value, str): return value return json.dumps(value) defence360agent/model/wp_disabled_rule.py0000644000000000000000000003601600000000000015532 0ustar """WordPress-specific disabled rules data model. This module provides a separate data model for WordPress disabled rules, independent of the existing DisabledRule/DisabledRuleDomain models used by modsec/ossec plugins. Disable Behavior: Global and domain-level disables are independent and can coexist. A rule is considered effectively disabled for a given WordPress domain if EITHER of these conditions is true: - A global disable exists for the rule (applies to all domains) - A domain-specific disable exists for the rule and that domain Enabling a rule at one scope does not affect disables at the other scope. For example, removing a global disable leaves any domain-specific disables intact, and vice versa. """ from collections.abc import Iterator import logging import time from peewee import ( CharField, FloatField, IntegerField, IntegrityError, PrimaryKeyField, fn, ) from defence360agent.model import Model, instance logger = logging.getLogger(__name__) class WPDisabledRule(Model): """Stores disabled WordPress protection rules. Uses a scope-based design: - scope='global', scope_value=NULL: Rule disabled for all domains (root only) - scope='domain', scope_value='example.com': Rule disabled for specific domain """ class Meta: database = instance.db db_table = "wp_disabled_rules" indexes = ((("rule_id", "scope", "scope_value"), True),) id = PrimaryKeyField() # The rule identifier (e.g., "CVE-2025-001") rule_id = CharField(null=False) # The scope type: "global" or "domain" scope = CharField(null=False) # The scope value: NULL for global, domain name for domain scope scope_value = CharField(null=True) # Unix timestamp when the rule was disabled disabled_at = FloatField(null=False) # Origin of the disable action: "wordpress" (from wordpress admin ui) or "agent" (from CLI/RPC) source = CharField(null=False) # UID of the user who disabled the rule (0 for root) created_by_user_id = IntegerField(null=False) # Scope constants SCOPE_GLOBAL = "global" SCOPE_DOMAIN = "domain" # Source constants SOURCE_WORDPRESS = "wordpress" SOURCE_AGENT = "agent" @classmethod def store( cls, rule_id: str, domains: list[str] | None, source: str, user_id: int, timestamp: float | None = None, ) -> int: """ Disable a rule globally or for specific domains. Args: rule_id: The rule identifier (e.g., "CVE-2025-001") domains: List of domains to disable for, or None/empty for global disable source: Origin of the action ("wordpress" or "agent") user_id: UID of the user performing the action (0 for root) timestamp: Unix timestamp for when the rule was disabled. If None, uses current time. Returns: Number of new entries created (0 if all were no-ops). """ if timestamp is None: timestamp = time.time() if domains: return cls._disable_for_domains( rule_id, domains, timestamp, source, user_id ) return cls._disable_globally(rule_id, timestamp, source, user_id) @classmethod def _disable_globally( cls, rule_id: str, timestamp: float, source: str, user_id: int, ) -> int: """Disable a rule globally (independent of domain-specific entries).""" created = cls._create_if_not_exists( rule_id=rule_id, scope=cls.SCOPE_GLOBAL, scope_value=None, disabled_at=timestamp, source=source, user_id=user_id, ) if created: logger.debug( "Disabled rule %s globally (source=%s, user_id=%s)", rule_id, source, user_id, ) return int(created) @classmethod def _disable_for_domains( cls, rule_id: str, domains: list[str], timestamp: float, source: str, user_id: int, ) -> int: """Disable a rule for specific domains (independent of global state).""" count = 0 for domain in domains: created = cls._create_if_not_exists( rule_id=rule_id, scope=cls.SCOPE_DOMAIN, scope_value=domain, disabled_at=timestamp, source=source, user_id=user_id, ) if created: count += 1 logger.debug( "Disabled rule %s for domain %s (source=%s, user_id=%s)", rule_id, domain, source, user_id, ) return count @classmethod def _create_if_not_exists( cls, rule_id: str, scope: str, scope_value: str | None, disabled_at: float, source: str, user_id: int, ) -> bool: """ Create a new disabled rule entry if it doesn't already exist. Returns: True if a new entry was created, False if it already existed (no-op) """ try: cls.insert( rule_id=rule_id, scope=scope, scope_value=scope_value, disabled_at=disabled_at, source=source, created_by_user_id=user_id, ).execute() return True except IntegrityError: # Rule already disabled for this scope - no-op return False @classmethod def remove(cls, rule_id: str, domains: list[str] | None) -> int: """ Re-enable a rule globally or for specific domains. Args: rule_id: The rule identifier domains: List of domains to enable for, or None/empty to enable globally Returns: Number of rows deleted """ if not domains: # Enable globally - remove ONLY the global entry count = ( cls.delete() .where( cls.rule_id == rule_id, cls.scope == cls.SCOPE_GLOBAL, ) .execute() ) if count: logger.debug("Enabled rule %s globally", rule_id) else: # Enable for specific domains count = ( cls.delete() .where( cls.rule_id == rule_id, cls.scope == cls.SCOPE_DOMAIN, cls.scope_value.in_(domains), ) .execute() ) if count: logger.debug( "Enabled rule %s for %d domain(s)", rule_id, count, ) return count @classmethod def is_rule_disabled(cls, rule_id: str, domain: str | None = None) -> bool: """ Check if a rule is disabled globally or for a specific domain. Args: rule_id: The rule identifier domain: The domain to check. If None, only checks global disable. Returns: True if the rule is disabled, False otherwise """ if domain is None: return ( cls.select() .where( cls.rule_id == rule_id, cls.scope == cls.SCOPE_GLOBAL, ) .exists() ) return ( cls.select() .where( cls.rule_id == rule_id, ( (cls.scope == cls.SCOPE_GLOBAL) | ( (cls.scope == cls.SCOPE_DOMAIN) & (cls.scope_value == domain) ) ), ) .exists() ) @classmethod def get_domain_disabled( cls, domain: str, include_global: bool = False ) -> list[str]: """ Get all rule IDs that are disabled for a specific domain. Args: domain: The domain to get disabled rules for include_global: If True, also include globally disabled rules. If False (default), only return domain-specific disables. Returns: List of rule IDs that are disabled for the domain """ if include_global: query = ( cls.select(cls.rule_id) .where( (cls.scope == cls.SCOPE_GLOBAL) | ( (cls.scope == cls.SCOPE_DOMAIN) & (cls.scope_value == domain) ) ) .distinct() ) else: query = cls.select(cls.rule_id).where( cls.scope == cls.SCOPE_DOMAIN, cls.scope_value == domain, ) return [row.rule_id for row in query] @classmethod def get_global_disabled(cls) -> Iterator[str]: """ Get all rule IDs that are disabled globally. Returns: Iterator of globally disabled rule IDs """ query = cls.select(cls.rule_id).where(cls.scope == cls.SCOPE_GLOBAL) return (row.rule_id for row in query) @classmethod def _build_filter_condition( cls, user_domains: list[str] | None, include_global: bool, ): """ Build the WHERE condition for filtering rules. Returns: A Peewee expression for the WHERE clause, or None if no filter needed. """ if user_domains is not None: domain_match = (cls.scope == cls.SCOPE_DOMAIN) & ( cls.scope_value.in_(user_domains) ) if include_global: return (cls.scope == cls.SCOPE_GLOBAL) | domain_match return domain_match if not include_global: return cls.scope == cls.SCOPE_DOMAIN return None @classmethod def fetch( cls, limit: int, offset: int = 0, user_domains: list[str] | None = None, include_global: bool = False, ) -> tuple[int, list[dict]]: """ List disabled rules with aggregation by rule_id. Multiple domain entries for the same rule are aggregated into a single result with a list of domains. Results are ordered by most recently disabled first (using the latest disabled_at timestamp per rule_id). Uses a two-pass approach for efficiency: 1. First pass: Get rule_ids ordered by latest disabled_at with pagination 2. Second pass: Fetch only rows for the paginated rule_ids Args: limit: Maximum number of rules to return offset: Number of rules to skip user_domains: If provided, only return rules for these domains. If None, return all rules (for root users). include_global: Whether to include global rules in the result Returns: Tuple of (total_count, list of rule dicts) Each dict has: {"rule_id": str, "is_global": bool, "domains": list[str]} is_global is True if rule has a global disable, domains lists domain-specific disables """ # Build filter condition condition = cls._build_filter_condition(user_domains, include_global) # First pass: get rule_ids ordered by latest disabled_at (most recent first) rule_ids_query = ( cls.select(cls.rule_id) .group_by(cls.rule_id) .order_by(fn.MAX(cls.disabled_at).desc()) ) if condition is not None: rule_ids_query = rule_ids_query.where(condition) # Get total count of distinct rule_ids total_count = rule_ids_query.count() # Apply pagination at DB level paginated_rule_ids = [ row.rule_id for row in rule_ids_query.offset(offset).limit(limit) ] if not paginated_rule_ids: return total_count, [] # Second pass: fetch rows for the paginated rule_ids rows_query = cls.select().where(cls.rule_id.in_(paginated_rule_ids)) if condition is not None: rows_query = rows_query.where(condition) # Aggregate domains by rule_id rules_by_id: dict[str, dict] = {} for row in rows_query: if row.rule_id not in rules_by_id: rules_by_id[row.rule_id] = { "rule_id": row.rule_id, "is_global": False, "domains": [], } if row.scope == cls.SCOPE_GLOBAL: rules_by_id[row.rule_id]["is_global"] = True elif row.scope == cls.SCOPE_DOMAIN: rules_by_id[row.rule_id]["domains"].append(row.scope_value) # Build result in order from first query (preserves DB ordering) result = [] for rule_id in paginated_rule_ids: rule_data = rules_by_id[rule_id] rule_data["domains"] = sorted(rule_data["domains"]) result.append(rule_data) return total_count, result def enrich_incidents_with_disabled_state(incidents: list[dict]) -> None: """Set is_rule_disabled on each incident dict in place. A rule is considered disabled for an incident when wp_disabled_rules has a row with rule_id == incident["rule"] AND (scope='global' OR (scope='domain' AND scope_value == incident["domain"])). Incidents with a NULL rule (legacy/imported rows) always get False. Runs at most one SELECT regardless of the input length. """ rule_ids = { inc["rule"] for inc in incidents if inc.get("rule") is not None } if not rule_ids: for inc in incidents: inc["is_rule_disabled"] = False return domains = { inc["domain"] for inc in incidents if inc.get("domain") is not None } condition = WPDisabledRule.scope == WPDisabledRule.SCOPE_GLOBAL if domains: condition = condition | ( (WPDisabledRule.scope == WPDisabledRule.SCOPE_DOMAIN) & (WPDisabledRule.scope_value.in_(domains)) ) query = WPDisabledRule.select( WPDisabledRule.rule_id, WPDisabledRule.scope, WPDisabledRule.scope_value, ).where(WPDisabledRule.rule_id.in_(rule_ids), condition) globally_disabled: set[str] = set() domain_disabled: set[tuple[str, str]] = set() for row in query: if row.scope == WPDisabledRule.SCOPE_GLOBAL: globally_disabled.add(row.rule_id) else: domain_disabled.add((row.rule_id, row.scope_value)) for inc in incidents: rule = inc.get("rule") domain = inc.get("domain") inc["is_rule_disabled"] = bool( rule is not None and ( rule in globally_disabled or (domain is not None and (rule, domain) in domain_disabled) ) ) defence360agent/mr_proper/0000755000000000000000000000000000000000000012553 5ustar defence360agent/mr_proper/__init__.py0000644000000000000000000000074100000000000014666 0ustar """ This package contains the definitions for cleaners. A cleaner can be used to delete old files, database entries, etc. All cleaners: - inherit from BaseCleaner (implement the interface) - should be used in the MrProper plugin to have any effect """ from abc import ABC, abstractmethod class BaseCleaner(ABC): @property @classmethod @abstractmethod def PERIOD(cls): pass @classmethod @abstractmethod async def cleanup(cls): pass defence360agent/mr_proper/__pycache__/0000755000000000000000000000000000000000000014763 5ustar defence360agent/mr_proper/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000231000000000000022157 0ustar r_j6dZddlmZmZGddeZdS)z This package contains the definitions for cleaners. A cleaner can be used to delete old files, database entries, etc. All cleaners: - inherit from BaseCleaner (implement the interface) - should be used in the MrProper plugin to have any effect )ABCabstractmethodcjeZdZeeedZeedZdS) BaseCleanercdSNclss W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/mr_proper/__init__.pyPERIODzBaseCleaner.PERIODs  c KdSrr r s r cleanupzBaseCleaner.cleanups  rN)__name__ __module__ __qualname__property classmethodrr rr rr rrsf   ^[X   ^[   rrN)__doc__abcrrrr rr rs]  $#######      #      rdefence360agent/mr_proper/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000231000000000000021220 0ustar r_j6dZddlmZmZGddeZdS)z This package contains the definitions for cleaners. A cleaner can be used to delete old files, database entries, etc. All cleaners: - inherit from BaseCleaner (implement the interface) - should be used in the MrProper plugin to have any effect )ABCabstractmethodcjeZdZeeedZeedZdS) BaseCleanercdSNclss W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/mr_proper/__init__.pyPERIODzBaseCleaner.PERIODs  c KdSrr r s r cleanupzBaseCleaner.cleanups  rN)__name__ __module__ __qualname__property classmethodrr rr rr rrsf   ^[X   ^[   rrN)__doc__abcrrrr rr rs]  $#######      #      rdefence360agent/myimunify/0000755000000000000000000000000000000000000012574 5ustar defence360agent/myimunify/__init__.py0000644000000000000000000000000000000000000014673 0ustar defence360agent/myimunify/__pycache__/0000755000000000000000000000000000000000000015004 5ustar defence360agent/myimunify/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030400000000000022201 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/__init__.pyrsrdefence360agent/myimunify/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030400000000000021242 0ustar r_jdS)NrW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/__init__.pyrsrdefence360agent/myimunify/__pycache__/billing.cpython-311.opt-1.pyc0000644000000000000000000000476500000000000022101 0ustar r_jddlmZmZddlmZeGddZeGddZeGddZd Zd Z d S) ) dataclassasdict)configceZdZdZdS) MILicenseTypeFreemiumN)__name__ __module__ __qualname__FREEMIUMV/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/billing.pyrrsHHHrrceZdZdZdZdS)IncompatibilityIDz= Contains unique incompatibilities IDs for a billing LICENSE_IS_NOT_SUPPORTEDN)r r r __doc__UNSUPPORTED_LICENSEr rrrr s"5rrc>eZdZUdZeed<eed<edZdS)CompatibilityIssuezC Generic class for keeping compatibility issues with WHMCS type descriptionc t|SN)r)selfs r dict_reprzCompatibilityIssue.dict_reprsd||rN)r r r rstr__annotations__propertyrr rrrrsP III XrrcDtjr tjSdSr)ris_mi_freemium_licenserr r rrget_license_typer"!s! $&&&%% 4rcKg}ttjkr3|t t jdj|S)z Collects all incompatibilities for WHMCS: 1. No Freemium license means WHMCS cannot configure current server 2. .... z5There is no supported MyImunify license on the server)rr)r"rr appendrrrr)issuess r!collect_billing_incompatibilitiesr&'s^ F]333 &:K         MrN) dataclassesrrdefence360agent.contractsrrrrr"r&r rrr)s)))))))),,,,,,   5555555 5           rdefence360agent/myimunify/__pycache__/billing.cpython-311.pyc0000644000000000000000000000476500000000000021142 0ustar r_jddlmZmZddlmZeGddZeGddZeGddZd Zd Z d S) ) dataclassasdict)configceZdZdZdS) MILicenseTypeFreemiumN)__name__ __module__ __qualname__FREEMIUMV/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/billing.pyrrsHHHrrceZdZdZdZdS)IncompatibilityIDz= Contains unique incompatibilities IDs for a billing LICENSE_IS_NOT_SUPPORTEDN)r r r __doc__UNSUPPORTED_LICENSEr rrrr s"5rrc>eZdZUdZeed<eed<edZdS)CompatibilityIssuezC Generic class for keeping compatibility issues with WHMCS type descriptionc t|SN)r)selfs r dict_reprzCompatibilityIssue.dict_reprsd||rN)r r r rstr__annotations__propertyrr rrrrsP III XrrcDtjr tjSdSr)ris_mi_freemium_licenserr r rrget_license_typer"!s! $&&&%% 4rcKg}ttjkr3|t t jdj|S)z Collects all incompatibilities for WHMCS: 1. No Freemium license means WHMCS cannot configure current server 2. .... z5There is no supported MyImunify license on the server)rr)r"rr appendrrrr)issuess r!collect_billing_incompatibilitiesr&'s^ F]333 &:K         MrN) dataclassesrrdefence360agent.contractsrrrrr"r&r rrr)s)))))))),,,,,,   5555555 5           rdefence360agent/myimunify/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000033300000000000022460 0ustar r_j dZdS) myimunifyN) MYIMUNIFYX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/constants.pyrs  rdefence360agent/myimunify/__pycache__/constants.cpython-311.pyc0000644000000000000000000000033300000000000021521 0ustar r_j dZdS) myimunifyN) MYIMUNIFYX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/constants.pyrs  rdefence360agent/myimunify/__pycache__/model.cpython-311.opt-1.pyc0000644000000000000000000001624200000000000021552 0ustar r_jNddlZddlZddlZddlmZmZddlmZmZddl m cm cm Z ddlmZddlmZmZddlmZddlmZmZddlmZejd d d ZejeZGd d eZdZ ddee!de"de"fdZ#de"fdZ$dee!de"fdZ%dS)N)ListOptional) BooleanField CharField) MessageType)Modelinstance)run_in_executor)execute_iterable_expressionimporter update_configzimav.malwarelib.model MalwareHit)modulenamedefaultceZdZdZGddZedZeddZe de e d e fd Z e d ee d e fd ZdS) MyImunifyzSecure-site related settingsc eZdZejZdZdS)MyImunify.Meta myimunifyN)__name__ __module__ __qualname__r dbdatabasedb_tableT/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/model.pyMetars;rr!T)uniqueF)nullruserreturnc\||dkrdS||ddi\}}|jS)z%Get SecureSite protection by usernameNrootT protectionF)r$defaults) get_or_creater()clsr$perm_s r get_protectionzMyImunify.get_protection$s? <46>>4##u8M#NNarusersstatusc|fd|D|jgg|jidS)Ncg|]}|dS))r$r(r).0r$r0s r z5MyImunify.update_users_protection..1s! D D Ddd& 1 1 D D Dr)conflict_targetpreserveupdate) insert_many on_conflictr$r(execute)r+r/r0s `r update_users_protectionz!MyImunify.update_users_protection.sd  D D D De D D D  + XJNF+    ')))))rN)rrr__doc__r!rr$rr( classmethodrstrboolr.rr;rrr rrs&& 9D ! ! !D5%888J(3-D[DIt[rrcKtdSt|d}|r0|tj|d{VdSdS)NT)r$cleanup)hits)rmalicious_selectprocess_messagerMalwareCleanupTask)sinkr$rBs r malware_cleanuprG9sx  & &D$ & ? ?D N"";#At#L#L#LMMMMMMMMMMMNNrFr/r0force_config_updatecKtdfdd{Vdsd|rtddiig}nfdD}r|fdDz }tj|d{VdS)Nc:tSN)rr;)r0r/sr z)update_users_protection..Fs 11%@@rLOGPROACTIVE_DEFENCEmodec:g|]}tddii|S)rNrOr )r3r$proactive_moderFs r r4z+update_users_protection..TsH    $v~&>?     rc0g|]}t|Sr)rG)r3r$rFs r r4z+update_users_protection..^s#@@@$/$--@@@r)r rasynciogather)rFr/r0rHtasksrQs``` @r r;r;As  @@@@@  N  $v~&>?            A @@@@%@@@@ .%          rcKtjd{V}t|||d{VdS)z#Set protection status for all usersN)hp HostingPanel get_usersr;)rFr0 panel_userss r #set_protection_status_for_all_usersr[cs[))3355555555K !$ V < <<<<<<<<<.expressionus9##%%++""?33 rzAdd permissions to users %sFN) setrselectr$ itertoolschaintuplesloggerinfor listr;r?)rFr/rZmyimunify_usersrarb users_to_adds r sync_usersrmise**K&&y~66O)/?+A+A+C+CDEEO% 3OG /AAA    $J_0E0EFFF0L: 1<@@@%dE5999999999    6o!6!66r)F)&rSreloggingtypingrrpeeweerr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrW"defence360agent.contracts.messagesrdefence360agent.modelrr $defence360agent.model.simplificationr defence360agent.utilsr r defence360agent.utils.configrgetr getLoggerrrhrrGr>r?r;r[rmrrr r|s!!!!!!!!********888888888888::::::11111111@@@@@@GGGGGGGG666666 X\ "t   8 $ $        FNNNGL!!c!$(!?C!!!!D=D==== 7$s)7777777rdefence360agent/myimunify/__pycache__/model.cpython-311.pyc0000644000000000000000000001624200000000000020613 0ustar r_jNddlZddlZddlZddlmZmZddlmZmZddl m cm cm Z ddlmZddlmZmZddlmZddlmZmZddlmZejd d d ZejeZGd d eZdZ ddee!de"de"fdZ#de"fdZ$dee!de"fdZ%dS)N)ListOptional) BooleanField CharField) MessageType)Modelinstance)run_in_executor)execute_iterable_expressionimporter update_configzimav.malwarelib.model MalwareHit)modulenamedefaultceZdZdZGddZedZeddZe de e d e fd Z e d ee d e fd ZdS) MyImunifyzSecure-site related settingsc eZdZejZdZdS)MyImunify.Meta myimunifyN)__name__ __module__ __qualname__r dbdatabasedb_tableT/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/model.pyMetars;rr!T)uniqueF)nullruserreturnc\||dkrdS||ddi\}}|jS)z%Get SecureSite protection by usernameNrootT protectionF)r$defaults) get_or_creater()clsr$perm_s r get_protectionzMyImunify.get_protection$s? <46>>4##u8M#NNarusersstatusc|fd|D|jgg|jidS)Ncg|]}|dS))r$r(r).0r$r0s r z5MyImunify.update_users_protection..1s! D D Ddd& 1 1 D D Dr)conflict_targetpreserveupdate) insert_many on_conflictr$r(execute)r+r/r0s `r update_users_protectionz!MyImunify.update_users_protection.sd  D D D De D D D  + XJNF+    ')))))rN)rrr__doc__r!rr$rr( classmethodrstrboolr.rr;rrr rrs&& 9D ! ! !D5%888J(3-D[DIt[rrcKtdSt|d}|r0|tj|d{VdSdS)NT)r$cleanup)hits)rmalicious_selectprocess_messagerMalwareCleanupTask)sinkr$rBs r malware_cleanuprG9sx  & &D$ & ? ?D N"";#At#L#L#LMMMMMMMMMMMNNrFr/r0force_config_updatecKtdfdd{Vdsd|rtddiig}nfdD}r|fdDz }tj|d{VdS)Nc:tSN)rr;)r0r/sr z)update_users_protection..Fs 11%@@rLOGPROACTIVE_DEFENCEmodec:g|]}tddii|S)rNrOr )r3r$proactive_moderFs r r4z+update_users_protection..TsH    $v~&>?     rc0g|]}t|Sr)rG)r3r$rFs r r4z+update_users_protection..^s#@@@$/$--@@@r)r rasynciogather)rFr/r0rHtasksrQs``` @r r;r;As  @@@@@  N  $v~&>?            A @@@@%@@@@ .%          rcKtjd{V}t|||d{VdS)z#Set protection status for all usersN)hp HostingPanel get_usersr;)rFr0 panel_userss r #set_protection_status_for_all_usersr[cs[))3355555555K !$ V < <<<<<<<<<.expressionus9##%%++""?33 rzAdd permissions to users %sFN) setrselectr$ itertoolschaintuplesloggerinfor listr;r?)rFr/rZmyimunify_usersrarb users_to_adds r sync_usersrmise**K&&y~66O)/?+A+A+C+CDEEO% 3OG /AAA    $J_0E0EFFF0L: 1<@@@%dE5999999999    6o!6!66r)F)&rSreloggingtypingrrpeeweerr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrW"defence360agent.contracts.messagesrdefence360agent.modelrr $defence360agent.model.simplificationr defence360agent.utilsr r defence360agent.utils.configrgetr getLoggerrrhrrGr>r?r;r[rmrrr r|s!!!!!!!!********888888888888::::::11111111@@@@@@GGGGGGGG666666 X\ "t   8 $ $        FNNNGL!!c!$(!?C!!!!D=D==== 7$s)7777777rdefence360agent/myimunify/advice/0000755000000000000000000000000000000000000014027 5ustar defence360agent/myimunify/advice/__init__.py0000644000000000000000000000000000000000000016126 0ustar defence360agent/myimunify/advice/__pycache__/0000755000000000000000000000000000000000000016237 5ustar defence360agent/myimunify/advice/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000031300000000000023434 0ustar r_jdS)Nr^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/__init__.pyrsrdefence360agent/myimunify/advice/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031300000000000022475 0ustar r_jdS)Nr^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/__init__.pyrsrdefence360agent/myimunify/advice/__pycache__/advice_manager.cpython-311.opt-1.pyc0000644000000000000000000002072600000000000024634 0ustar r_jddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z ddlmZdd lmZejeZd Zd Zd Zd efdZd egfdZdS)N) find_wp_paths) EventsAPI)config)get_myimunify_users)get_upgrade_url_link) HostingPanel)MyImunifyWPAdvice) MyImunifyIMUNIFY_PROTECTIONc|g}tjtj|}|r|dddrdSdS)Nr protectionFactiveno)r selectwhereuserin_dictsget)usernameitemresponses d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/advice_manager.pyget_myimunify_protection_statusrsl :D!!'' (:(:4(@(@AAGGIIHHQKOOL%88xtczKg}|d}t|}|d}|d}|d}|d}|d}|d} t|d{V} t|D]} d | } t j|d |d | d } | d | }td"id |d|d| d| dzd|dtddd|d|ddddddd|d|dd d!d }| | |S)#a imunify advice item: {"id": 123, "server_id": null, "type": "malware_found_myimun_2", "date": 123, "severity": 1, "translation_id": "1", "parameters": {}, "description": null, "link_text": null, "link": null, "dashboard": false, "popup": false, "snoozed_until": 0, "popup_title": null, "popup_description": null, "config_action": {}, "ignore": {}, "notification": false, "smartadvice": true, "smartadvice_title": "Web hosting user account is infected", "smartadvice_description": " Imunify detected live malware on the user account hosting this website: * inf1 * inf2 ", "smartadvice_user": "isuser", "smartadvice_domain": "isuser.com", "smartadvice_docroot": "/", "ts": 123, "first_generated": 123, "iaid": "agent-iaid-123", "notification_body_html": null, "notification_period_limit": 0, "notification_subject": null, "notification_user": null} -> { "created_at": "2024-10-02T01:22:11.918688+00:00", "updated_at": "2024-10-02T01:22:11.918688+00:00", "metadata": { "app": "imunify" "username": "tkcpanel", "domain": "tk-cpanel.com", "website": "/", "panel_url": "https://10.193.176.2:2083/cpsess0000000000/frontend/paper_lantern/lveversion/wpos.live.pl", }, "advice": { "id": "287718", "type": "CPCSS", "status": "review", "description": "Turn on Critical Path CSS", "is_premium": true, "module_name": "critical_css", "license_status": "NOT_REQUIRED", "subscription": { "status": "active", "upgrade_url": "https://whmcs.dev.cloudlinux.com?username=tkcpanel&domain=tk-cpanel.com&server_ip=10.193.176.2&m=cloudlinux_advantage&action=provisioning&suite=accelerate_wp_premium" }, "total_stages": 0, "completed_stages": 0, "detailed_description": "Critical Path CSS eliminates render-blocking CSS on your website and improves browser page render performance. Your website will load much faster for your visitors. Note: Applying the current advice will also enable the AccelerateWP feature." } } smartadvice_usersmartadvice_domainsmartadvice_docroot upgrade_urlsmartadvice_titlesmartadvice_descriptioniaidN/-zutf-8_rdomainwebsite panel_urlz?show_cleanup_dialog=trueidtypestatusreview descriptiondetailed_description is_premiumF module_nameimunifylicense_status NOT_REQUIREDsubscription_status total_stagesrcompleted_stages) rrpanel_user_linkrhashlibmd5encode hexdigestr ADV_TYPEappend to_advice)imunify_adviceadvices_by_docrootrprotection_statusr'infected_docrootr r.r/r#r)siter( hashed_udwadv_idim360_protection_advices r _make_advicerIs|01H7AA 0 1F%&;< /K !45K)*CD & !D"nn44X>>>>>>>>I.//GGd**[ , ,& , ,7 , , 3 3G < <  )++ '':''"3# # # X# 6# G#  )** # v # # 8# $ # "6!5# u# " # *># !2 1# $ # !# "Q## & !!"9"C"C"E"EFFFF rreturnc Kg}td{V}tdt||r~|D]{} t |d{V}nM#t $r@}tdt|t|Yd}~^d}~wwxYw||||S)NzIM360 advice list: %szz,get_advice_notifications..s8    %     r CONTROL_PANELsmart_advice_allowedzLSmart Advice events API response with notifications: %s, users to report: %scBg|]}|dv|S)r)r)rZeventusers_to_reports rr[z,get_advice_notifications..s;     99' ( (O ; ;  ; ; ;rrrr ) setrr ConfigFileraddr smart_advicesrMrNrOr) users_to_poprconfrdatarr`s @rrLrLso  133333333   O55L## &&xx)?@@ #   T " " "% 4O,........H KK 6 H  O         D   2 HH' ( ($((3G*H*H  ] Kr)loggingr:clcommon.clwpos_libr!defence360agent.api.server.eventsrdefence360agent.contractsr&defence360agent.contracts.myimunify_idrdefence360agent.utils.whmcsr+defence360agent.subsys.panels.hosting_panelr*defence360agent.myimunify.advice.dataclassr defence360agent.myimunify.modelr getLogger__name__rMr>rrIlistrWdictrLr8rrrus%------777777,,,,,,FFFFFF<<<<<<DDDDDDIHHHHH555555  8 $ $ cccL4(rdefence360agent/myimunify/advice/__pycache__/advice_manager.cpython-311.pyc0000644000000000000000000002072600000000000023675 0ustar r_jddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z ddlmZdd lmZejeZd Zd Zd Zd efdZd egfdZdS)N) find_wp_paths) EventsAPI)config)get_myimunify_users)get_upgrade_url_link) HostingPanel)MyImunifyWPAdvice) MyImunifyIMUNIFY_PROTECTIONc|g}tjtj|}|r|dddrdSdS)Nr protectionFactiveno)r selectwhereuserin_dictsget)usernameitemresponses d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/advice_manager.pyget_myimunify_protection_statusrsl :D!!'' (:(:4(@(@AAGGIIHHQKOOL%88xtczKg}|d}t|}|d}|d}|d}|d}|d}|d} t|d{V} t|D]} d | } t j|d |d | d } | d | }td"id |d|d| d| dzd|dtddd|d|ddddddd|d|dd d!d }| | |S)#a imunify advice item: {"id": 123, "server_id": null, "type": "malware_found_myimun_2", "date": 123, "severity": 1, "translation_id": "1", "parameters": {}, "description": null, "link_text": null, "link": null, "dashboard": false, "popup": false, "snoozed_until": 0, "popup_title": null, "popup_description": null, "config_action": {}, "ignore": {}, "notification": false, "smartadvice": true, "smartadvice_title": "Web hosting user account is infected", "smartadvice_description": " Imunify detected live malware on the user account hosting this website: * inf1 * inf2 ", "smartadvice_user": "isuser", "smartadvice_domain": "isuser.com", "smartadvice_docroot": "/", "ts": 123, "first_generated": 123, "iaid": "agent-iaid-123", "notification_body_html": null, "notification_period_limit": 0, "notification_subject": null, "notification_user": null} -> { "created_at": "2024-10-02T01:22:11.918688+00:00", "updated_at": "2024-10-02T01:22:11.918688+00:00", "metadata": { "app": "imunify" "username": "tkcpanel", "domain": "tk-cpanel.com", "website": "/", "panel_url": "https://10.193.176.2:2083/cpsess0000000000/frontend/paper_lantern/lveversion/wpos.live.pl", }, "advice": { "id": "287718", "type": "CPCSS", "status": "review", "description": "Turn on Critical Path CSS", "is_premium": true, "module_name": "critical_css", "license_status": "NOT_REQUIRED", "subscription": { "status": "active", "upgrade_url": "https://whmcs.dev.cloudlinux.com?username=tkcpanel&domain=tk-cpanel.com&server_ip=10.193.176.2&m=cloudlinux_advantage&action=provisioning&suite=accelerate_wp_premium" }, "total_stages": 0, "completed_stages": 0, "detailed_description": "Critical Path CSS eliminates render-blocking CSS on your website and improves browser page render performance. Your website will load much faster for your visitors. Note: Applying the current advice will also enable the AccelerateWP feature." } } smartadvice_usersmartadvice_domainsmartadvice_docroot upgrade_urlsmartadvice_titlesmartadvice_descriptioniaidN/-zutf-8_rdomainwebsite panel_urlz?show_cleanup_dialog=trueidtypestatusreview descriptiondetailed_description is_premiumF module_nameimunifylicense_status NOT_REQUIREDsubscription_status total_stagesrcompleted_stages) rrpanel_user_linkrhashlibmd5encode hexdigestr ADV_TYPEappend to_advice)imunify_adviceadvices_by_docrootrprotection_statusr'infected_docrootr r.r/r#r)siter( hashed_udwadv_idim360_protection_advices r _make_advicerIs|01H7AA 0 1F%&;< /K !45K)*CD & !D"nn44X>>>>>>>>I.//GGd**[ , ,& , ,7 , , 3 3G < <  )++ '':''"3# # # X# 6# G#  )** # v # # 8# $ # "6!5# u# " # *># !2 1# $ # !# "Q## & !!"9"C"C"E"EFFFF rreturnc Kg}td{V}tdt||r~|D]{} t |d{V}nM#t $r@}tdt|t|Yd}~^d}~wwxYw||||S)NzIM360 advice list: %szz,get_advice_notifications..s8    %     r CONTROL_PANELsmart_advice_allowedzLSmart Advice events API response with notifications: %s, users to report: %scBg|]}|dv|S)r)r)rZeventusers_to_reports rr[z,get_advice_notifications..s;     99' ( (O ; ;  ; ; ;rrrr ) setrr ConfigFileraddr smart_advicesrMrNrOr) users_to_poprconfrdatarr`s @rrLrLso  133333333   O55L## &&xx)?@@ #   T " " "% 4O,........H KK 6 H  O         D   2 HH' ( ($((3G*H*H  ] Kr)loggingr:clcommon.clwpos_libr!defence360agent.api.server.eventsrdefence360agent.contractsr&defence360agent.contracts.myimunify_idrdefence360agent.utils.whmcsr+defence360agent.subsys.panels.hosting_panelr*defence360agent.myimunify.advice.dataclassr defence360agent.myimunify.modelr getLogger__name__rMr>rrIlistrWdictrLr8rrrus%------777777,,,,,,FFFFFF<<<<<<DDDDDDIHHHHH555555  8 $ $ cccL4(rdefence360agent/myimunify/advice/__pycache__/dataclass.cpython-311.opt-1.pyc0000644000000000000000000000522500000000000023643 0ustar r_j\ddlmZmZddlmZmZddlmZeGddZdS)) dataclassfield)datetimetimezone)OptionalceZdZUeed<eed<eed<eed<eed<eed<eed<eed<eed <eed <eed <eed <eed <eed<eed<eed<edZee ed<edZ ee ed<dZ dS)MyImunifyWPAdviceusernamedomainwebsite panel_urlidtypestatus descriptiondetailed_description is_premium module_namelicense_statussubscription_status upgrade_url total_stagescompleted_stagescbtjtjSNrnowrutc isoformat_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/dataclass.pyzMyImunifyWPAdvice. X\ : : D D F Fr!)default_factory created_atcbtjtjSrrr r!r"r#zMyImunifyWPAdvice.r$r! updated_atc|j|jd|j|j|j|jd|j|j|j|j |j |j |j |j |jd|j|j|jd dS)Nimunify)appr r r r )rr) rrrrrrr subscriptionrrr)r&r(metadataadvice)r&r(r r r r rrrrrrrrrrrr)selfs r" to_advicezMyImunifyWPAdvice.to_advices//  M+<!^ g +#/"o#/"&"5"6#'#3!!!% 1$($9(,(A   r!N) __name__ __module__ __qualname__str__annotations__intrr&rrr(r0r r!r"r r s3MMM KKK LLLNNN GGG III KKKOOO%*UFF&&&J"&+UFF&&&J"     r!r N) dataclassesrrrrtypingrr r r!r"r9s(((((((('''''''' 3 3 3 3 3 3 3  3 3 3 r!defence360agent/myimunify/advice/__pycache__/dataclass.cpython-311.pyc0000644000000000000000000000522500000000000022704 0ustar r_j\ddlmZmZddlmZmZddlmZeGddZdS)) dataclassfield)datetimetimezone)OptionalceZdZUeed<eed<eed<eed<eed<eed<eed<eed<eed <eed <eed <eed <eed <eed<eed<eed<edZee ed<edZ ee ed<dZ dS)MyImunifyWPAdviceusernamedomainwebsite panel_urlidtypestatus descriptiondetailed_description is_premium module_namelicense_statussubscription_status upgrade_url total_stagescompleted_stagescbtjtjSNrnowrutc isoformat_/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/dataclass.pyzMyImunifyWPAdvice. X\ : : D D F Fr!)default_factory created_atcbtjtjSrrr r!r"r#zMyImunifyWPAdvice.r$r! updated_atc|j|jd|j|j|j|jd|j|j|j|j |j |j |j |j |jd|j|j|jd dS)Nimunify)appr r r r )rr) rrrrrrr subscriptionrrr)r&r(metadataadvice)r&r(r r r r rrrrrrrrrrrr)selfs r" to_advicezMyImunifyWPAdvice.to_advices//  M+<!^ g +#/"o#/"&"5"6#'#3!!!% 1$($9(,(A   r!N) __name__ __module__ __qualname__str__annotations__intrr&rrr(r0r r!r"r r s3MMM KKK LLLNNN GGG III KKKOOO%*UFF&&&J"&+UFF&&&J"     r!r N) dataclassesrrrrtypingrr r r!r"r9s(((((((('''''''' 3 3 3 3 3 3 3  3 3 3 r!defence360agent/myimunify/advice/__pycache__/hosting_smart_advice_api.cpython-311.opt-1.pyc0000644000000000000000000000247500000000000026735 0ustar r_jpTddlZddlZddlmZmZejeZdZdZ dZ dS)N) CheckRunError check_runzcl-hosting-smart-adviceimunifyc4Ktj|} ttddtd|gd{V}tj|}|ddS#t$r&}t d|Yd}~dSd}~wwxYw)Nsyncz--appz--jsonsuccessFz9Failed to sync advices with `cl-hosting-smart-advice`: %s) jsondumpsr EXECUTABLEAPP_NAMEloadsgetrloggerwarning)advicespayloadoutresultes n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/hosting_smart_advice_api.py sync_advicesr sj!!G , (Hg F        Czz)U+++  G   uuuuu s%A'' B1BB) r loggingdefence360agent.utilsrr getLogger__name__rr r rrrsd ::::::::  8 $ $ &  , , , , ,rdefence360agent/myimunify/advice/__pycache__/hosting_smart_advice_api.cpython-311.pyc0000644000000000000000000000247500000000000025776 0ustar r_jpTddlZddlZddlmZmZejeZdZdZ dZ dS)N) CheckRunError check_runzcl-hosting-smart-adviceimunifyc4Ktj|} ttddtd|gd{V}tj|}|ddS#t$r&}t d|Yd}~dSd}~wwxYw)Nsyncz--appz--jsonsuccessFz9Failed to sync advices with `cl-hosting-smart-advice`: %s) jsondumpsr EXECUTABLEAPP_NAMEloadsgetrloggerwarning)advicespayloadoutresultes n/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/myimunify/advice/hosting_smart_advice_api.py sync_advicesr sj!!G , (Hg F        Czz)U+++  G   uuuuu s%A'' B1BB) r loggingdefence360agent.utilsrr getLogger__name__rr r rrrsd ::::::::  8 $ $ &  , , , , ,rdefence360agent/myimunify/advice/advice_manager.py0000644000000000000000000001431200000000000017327 0ustar import logging import hashlib from clcommon.clwpos_lib import find_wp_paths from defence360agent.api.server.events import EventsAPI from defence360agent.contracts import config from defence360agent.contracts.myimunify_id import get_myimunify_users from defence360agent.utils.whmcs import get_upgrade_url_link from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.myimunify.advice.dataclass import MyImunifyWPAdvice from defence360agent.myimunify.model import MyImunify logger = logging.getLogger(__name__) ADV_TYPE = "IMUNIFY_PROTECTION" def get_myimunify_protection_status(username): item = [username] response = MyImunify.select().where(MyImunify.user.in_(item)).dicts() if response and response[0].get("protection", False): return "active" else: return "no" async def _make_advice(imunify_advice): """ imunify advice item: {"id": 123, "server_id": null, "type": "malware_found_myimun_2", "date": 123, "severity": 1, "translation_id": "1", "parameters": {}, "description": null, "link_text": null, "link": null, "dashboard": false, "popup": false, "snoozed_until": 0, "popup_title": null, "popup_description": null, "config_action": {}, "ignore": {}, "notification": false, "smartadvice": true, "smartadvice_title": "Web hosting user account is infected", "smartadvice_description": "\nImunify detected live malware on the user account hosting this website:\n\n* inf1\n\n* inf2\n", "smartadvice_user": "isuser", "smartadvice_domain": "isuser.com", "smartadvice_docroot": "/", "ts": 123, "first_generated": 123, "iaid": "agent-iaid-123", "notification_body_html": null, "notification_period_limit": 0, "notification_subject": null, "notification_user": null} -> { "created_at": "2024-10-02T01:22:11.918688+00:00", "updated_at": "2024-10-02T01:22:11.918688+00:00", "metadata": { "app": "imunify" "username": "tkcpanel", "domain": "tk-cpanel.com", "website": "/", "panel_url": "https://10.193.176.2:2083/cpsess0000000000/frontend/paper_lantern/lveversion/wpos.live.pl", }, "advice": { "id": "287718", "type": "CPCSS", "status": "review", "description": "Turn on Critical Path CSS", "is_premium": true, "module_name": "critical_css", "license_status": "NOT_REQUIRED", "subscription": { "status": "active", "upgrade_url": "https://whmcs.dev.cloudlinux.com?username=tkcpanel&domain=tk-cpanel.com&server_ip=10.193.176.2&m=cloudlinux_advantage&action=provisioning&suite=accelerate_wp_premium" }, "total_stages": 0, "completed_stages": 0, "detailed_description": "Critical Path CSS eliminates render-blocking CSS on your website and improves browser page render performance. Your website will load much faster for your visitors.\nNote: Applying the current advice will also enable the AccelerateWP feature." } } """ advices_by_docroot = [] username = imunify_advice["smartadvice_user"] protection_status = get_myimunify_protection_status(username) domain = imunify_advice["smartadvice_domain"] infected_docroot = imunify_advice["smartadvice_docroot"] upgrade_url = imunify_advice["upgrade_url"] description = imunify_advice["smartadvice_title"] detailed_description = imunify_advice["smartadvice_description"] iaid = imunify_advice["iaid"] panel_url = await HostingPanel().panel_user_link(username) for site in find_wp_paths(infected_docroot): website = f"/{site}" # for analytics: iaid-hash(username-domain-website) hashed_udw = hashlib.md5( f"{username}-{domain}-{website}".encode("utf-8") ).hexdigest() adv_id = f"{iaid}_{hashed_udw}" im360_protection_advice = MyImunifyWPAdvice( username=username, domain=domain, website=website, panel_url=panel_url + "?show_cleanup_dialog=true", # Flag tells UI to display cleanup dialog id=adv_id, type=ADV_TYPE, status="review", description=description, detailed_description=detailed_description, is_premium=False, module_name="imunify", license_status="NOT_REQUIRED", subscription_status=protection_status, upgrade_url=upgrade_url, total_stages=0, completed_stages=0, ) advices_by_docroot.append(im360_protection_advice.to_advice()) return advices_by_docroot async def make_advice() -> list: advices = [] advice_list = await get_advice_notifications() logger.info("IM360 advice list: %s", str(advice_list)) if advice_list: for item in advice_list: try: advice_item = await _make_advice(item) except KeyError as e: logger.error( "Unable to make advice based on item: %s, malformed" " error: %s", str(item), str(e), ) continue advices.extend(advice_item) return advices async def get_advice_notifications() -> [dict]: users_to_report = set( [ item["username"] for item in await get_myimunify_users() if not item["protection"] ] ) users_to_pop = set() for user in users_to_report: conf = config.ConfigFile(user) if not conf.get("CONTROL_PANEL", "smart_advice_allowed"): users_to_pop.add(user) users_to_report = users_to_report - users_to_pop response = await EventsAPI.smart_advices() logger.info( "Smart Advice events API response " "with notifications: %s, users to report: %s", str(response), str(users_to_report), ) data = [ event for event in response if event.get("smartadvice_user") in users_to_report ] for item in data: item["upgrade_url"] = get_upgrade_url_link( item.get("smartadvice_user"), item.get("smartadvice_domain") ) return data defence360agent/myimunify/advice/dataclass.py0000644000000000000000000000336400000000000016346 0ustar from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Optional @dataclass class MyImunifyWPAdvice: username: str domain: str website: str panel_url: str id: int type: str status: str description: str detailed_description: str is_premium: str module_name: str license_status: str subscription_status: str upgrade_url: str total_stages: int completed_stages: int created_at: Optional[datetime] = field( default_factory=lambda: datetime.now(timezone.utc).isoformat() ) updated_at: Optional[datetime] = field( default_factory=lambda: datetime.now(timezone.utc).isoformat() ) def to_advice(self): return { "created_at": self.created_at, "updated_at": self.updated_at, "metadata": { "app": "imunify", "username": self.username, "domain": self.domain, "website": self.website, "panel_url": self.panel_url, }, "advice": { "id": self.id, "type": self.type, "status": self.status, "description": self.description, "is_premium": self.is_premium, "module_name": self.module_name, "license_status": self.license_status, "subscription": { "status": self.subscription_status, "upgrade_url": self.upgrade_url, }, "total_stages": self.total_stages, "completed_stages": self.completed_stages, "detailed_description": self.detailed_description, }, } defence360agent/myimunify/advice/hosting_smart_advice_api.py0000644000000000000000000000116000000000000021424 0ustar import json import logging from defence360agent.utils import CheckRunError, check_run logger = logging.getLogger(__name__) EXECUTABLE = "cl-hosting-smart-advice" APP_NAME = "imunify" async def sync_advices(advices): payload = json.dumps(advices) try: out = await check_run( [EXECUTABLE, "sync", "--app", APP_NAME, "--json", payload] ) except CheckRunError as e: logger.warning( "Failed to sync advices with `cl-hosting-smart-advice`: %s", e ) return False else: result = json.loads(out) return result.get("success", False) defence360agent/myimunify/billing.py0000644000000000000000000000226700000000000014575 0ustar from dataclasses import dataclass, asdict from defence360agent.contracts import config @dataclass class MILicenseType: FREEMIUM = "Freemium" @dataclass class IncompatibilityID: """ Contains unique incompatibilities IDs for a billing """ UNSUPPORTED_LICENSE = "LICENSE_IS_NOT_SUPPORTED" @dataclass class CompatibilityIssue: """ Generic class for keeping compatibility issues with WHMCS """ type: str description: str @property def dict_repr(self): return asdict(self) def get_license_type(): if config.is_mi_freemium_license(): return MILicenseType.FREEMIUM return None async def collect_billing_incompatibilities(): """ Collects all incompatibilities for WHMCS: 1. No Freemium license means WHMCS cannot configure current server 2. .... """ issues = [] if get_license_type() != MILicenseType.FREEMIUM: issues.append( CompatibilityIssue( type=IncompatibilityID.UNSUPPORTED_LICENSE, description=( "There is no supported MyImunify license on the server" ), ).dict_repr ) return issues defence360agent/myimunify/constants.py0000644000000000000000000000003000000000000015153 0ustar MYIMUNIFY = "myimunify" defence360agent/myimunify/model.py0000644000000000000000000000743700000000000014261 0ustar import asyncio import itertools import logging from typing import List, Optional from peewee import BooleanField, CharField import defence360agent.subsys.panels.hosting_panel as hp from defence360agent.contracts.messages import MessageType from defence360agent.model import Model, instance from defence360agent.model.simplification import run_in_executor from defence360agent.utils import execute_iterable_expression, importer from defence360agent.utils.config import update_config MalwareHit = importer.get( module="imav.malwarelib.model", name="MalwareHit", default=None ) logger = logging.getLogger(__name__) class MyImunify(Model): """Secure-site related settings""" class Meta: database = instance.db db_table = "myimunify" #: The username of the end-user, or an empty string for the default value #: for all new users. user = CharField(unique=True) #: Is MyImunify protection enabled/disabled to the end-user. protection = BooleanField(null=False, default=False) @classmethod def get_protection(cls, user: Optional[str]) -> bool: """Get SecureSite protection by username""" if user is None or user == "root": # root return True perm, _ = cls.get_or_create(user=user, defaults={"protection": False}) return perm.protection @classmethod def update_users_protection(cls, users: List[str], status: bool): cls.insert_many( [{"user": user, "protection": status} for user in users] ).on_conflict( conflict_target=[cls.user], preserve=[], update={cls.protection: status}, ).execute() async def malware_cleanup(sink, user): if MalwareHit is None: return hits = MalwareHit.malicious_select(user=user, cleanup=True) if hits: await sink.process_message(MessageType.MalwareCleanupTask(hits=hits)) async def update_users_protection( sink, users: List[str], status: bool, force_config_update: bool = False ): await run_in_executor( None, lambda: MyImunify.update_users_protection(users, status), ) proactive_mode = None if not status: proactive_mode = "LOG" if force_config_update: tasks = [ update_config( sink, {"PROACTIVE_DEFENCE": {"mode": proactive_mode}}, ) ] else: tasks = [ update_config( sink, {"PROACTIVE_DEFENCE": {"mode": proactive_mode}}, user, ) for user in users ] if status: tasks += [malware_cleanup(sink, user) for user in users] await asyncio.gather(*tasks) async def set_protection_status_for_all_users(sink, status: bool): """Set protection status for all users""" panel_users = await hp.HostingPanel().get_users() await update_users_protection(sink, panel_users, status) async def sync_users(sink, users: List[str]) -> bool: """Synchronize existing permissions with myimunify users""" panel_users = set(users) myimunify_users = MyImunify.select(MyImunify.user) myimunify_users = set(itertools.chain(*myimunify_users.tuples())) users_to_remove = myimunify_users - panel_users if users_to_remove: logger.info("Remove myimunify users %s", users_to_remove) def expression(users_to_remove): return MyImunify.delete().where( MyImunify.user.in_(users_to_remove) ) execute_iterable_expression(expression, list(users_to_remove)) users_to_add = panel_users - myimunify_users if users_to_add: logger.info("Add permissions to users %s", users_to_add) await update_users_protection(sink, users, False) return bool(users_to_add) or bool(users_to_remove) defence360agent/plugins/0000755000000000000000000000000000000000000012227 5ustar defence360agent/plugins/__init__.py0000644000000000000000000000000000000000000014326 0ustar defence360agent/plugins/__pycache__/0000755000000000000000000000000000000000000014437 5ustar defence360agent/plugins/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030200000000000021632 0ustar r_jdS)NrU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/__init__.pyrsrdefence360agent/plugins/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030200000000000020673 0ustar r_jdS)NrU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/__init__.pyrsrdefence360agent/plugins/__pycache__/accumulate.cpython-311.opt-1.pyc0000644000000000000000000001417700000000000022235 0ustar r_jddlZddlZddlZddlmZddlmZddlmZm Z m Z ddl m Z m Z mZddlmZmZeeZGdde e ZdS) N) getLogger) inactivity) Accumulatable MessageType Splittable) MessageSink MessageSourceexpect)recurring_checksafe_cancel_taskc&eZdZejjZdZee j ddZ ee j ddZ e e ffd ZdZdZd Zd Zeejd efd Zd ZxZS) Accumulate%IMUNIFY360_AGGREGATE_MESSAGES_TIMEOUT<*IMUNIFY360_AGGREGATE_SHUTDOWN_SEND_TIMEOUT2c tjdi|||_||_t jt |_dS)N)super__init___period_shutdown_timeout collections defaultdictlist_data)selfperiodshutdown_timeoutkwargs __class__s W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/accumulate.pyrzAccumulate.__init__"sG ""6""" !1 ,T22 cK||_||_|jdkrdn<|t |j|j|_dS)Nr)_loop_sinkr create_taskr _flush_task)rloopsinks r# create_sourcezAccumulate.create_source-sf  |q   D!!"L"?/$,"?"? "L"L"N"NOO r$cK||_dS)N)r&)rr+s r# create_sinkzAccumulate.create_sink6s r$c$K tj||jd{VdS#tj$rHt d|j|jt|jd{VYdSYdSwxYw)Nz5Timeout (%ss) sending messages to server on shutdown.) asynciowait_forstopr TimeoutErrorloggererrorr*r rs r#shutdownzAccumulate.shutdown9s 3"499;;0FGG G G G G G G G G G# 3 3 3 LLG&   z%&tz222222222222&%%  3s28ABBcKtd|jt|jd{Vtd|d{VdS)NzAccumulate.stop cancel _taskzAccumulate.stop wait lock)r5infor*r r)r7s r#r3zAccumulate.stopEs| 2333 : !"4:.. . . . . . . . /000kkmmr$messagecDKt|jtr|jn|jf}|r`tjd5|D]"}|j||# ddddS#1swxYwYdSdS)N accumulate) isinstance LIST_CLASStuple do_accumulatertracktaskrappend)rr; list_types list_types r#collectzAccumulate.collectMs',e44 'G  $&   " " :!&&|44 : :!+::IJy)009999: : : : : : : : : : : : : : : : : : : : :s&BBBc K|j}tjt|_|D]\}}t |t r||n|f}|D]}t d|j dt|d |j ||d{Va#t$rtd||wxYwdS)NzPrepare z() for further processing)itemsz%s, %s)rrrrrI issubclassrbatchedr5r:__name__lenr'process_message TypeErrorr6)r copy_datarFmessagesrKbatchs r#r)zAccumulate._flushYsBJ  ,T22 #,??#4#4   Ixi44! !!(+++[  !   -y1--3u::---*44YYU5K5K5KLLLLLLLLLL LL9e<<<   s &*C(C9)rL __module__ __qualname__rProcessingOrderPOST_PROCESS_MESSAGEPROCESSING_ORDERSHUTDOWN_PRIORITYintosenvirongetDEFAULT_AGGREGATE_TIMEOUTSHUTDOWN_SEND_TIMEOUTrr-r/r8r3r rrrGr) __classcell__)r"s@r#rrs)"2G !$ >CC!! C CRHH ). 3 3 3 3 3 3    3 3 3 VK %&& :] : : :'& :r$r)r1rrZloggingrdefence360agent.apir"defence360agent.contracts.messagesrrr!defence360agent.contracts.pluginsrr r defence360agent.utilsr r rLr5rrr$r#res ******  DCCCCCCC 8  ZZZZZmZZZZZr$defence360agent/plugins/__pycache__/accumulate.cpython-311.pyc0000644000000000000000000001417700000000000021276 0ustar r_jddlZddlZddlZddlmZddlmZddlmZm Z m Z ddl m Z m Z mZddlmZmZeeZGdde e ZdS) N) getLogger) inactivity) Accumulatable MessageType Splittable) MessageSink MessageSourceexpect)recurring_checksafe_cancel_taskc&eZdZejjZdZee j ddZ ee j ddZ e e ffd ZdZdZd Zd Zeejd efd Zd ZxZS) Accumulate%IMUNIFY360_AGGREGATE_MESSAGES_TIMEOUT<*IMUNIFY360_AGGREGATE_SHUTDOWN_SEND_TIMEOUT2c tjdi|||_||_t jt |_dS)N)super__init___period_shutdown_timeout collections defaultdictlist_data)selfperiodshutdown_timeoutkwargs __class__s W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/accumulate.pyrzAccumulate.__init__"sG ""6""" !1 ,T22 cK||_||_|jdkrdn<|t |j|j|_dS)Nr)_loop_sinkr create_taskr _flush_task)rloopsinks r# create_sourcezAccumulate.create_source-sf  |q   D!!"L"?/$,"?"? "L"L"N"NOO r$cK||_dS)N)r&)rr+s r# create_sinkzAccumulate.create_sink6s r$c$K tj||jd{VdS#tj$rHt d|j|jt|jd{VYdSYdSwxYw)Nz5Timeout (%ss) sending messages to server on shutdown.) asynciowait_forstopr TimeoutErrorloggererrorr*r rs r#shutdownzAccumulate.shutdown9s 3"499;;0FGG G G G G G G G G G# 3 3 3 LLG&   z%&tz222222222222&%%  3s28ABBcKtd|jt|jd{Vtd|d{VdS)NzAccumulate.stop cancel _taskzAccumulate.stop wait lock)r5infor*r r)r7s r#r3zAccumulate.stopEs| 2333 : !"4:.. . . . . . . . /000kkmmr$messagecDKt|jtr|jn|jf}|r`tjd5|D]"}|j||# ddddS#1swxYwYdSdS)N accumulate) isinstance LIST_CLASStuple do_accumulatertracktaskrappend)rr; list_types list_types r#collectzAccumulate.collectMs',e44 'G  $&   " " :!&&|44 : :!+::IJy)009999: : : : : : : : : : : : : : : : : : : : :s&BBBc K|j}tjt|_|D]\}}t |t r||n|f}|D]}t d|j dt|d |j ||d{Va#t$rtd||wxYwdS)NzPrepare z() for further processing)itemsz%s, %s)rrrrrI issubclassrbatchedr5r:__name__lenr'process_message TypeErrorr6)r copy_datarFmessagesrKbatchs r#r)zAccumulate._flushYsBJ  ,T22 #,??#4#4   Ixi44! !!(+++[  !   -y1--3u::---*44YYU5K5K5KLLLLLLLLLL LL9e<<<   s &*C(C9)rL __module__ __qualname__rProcessingOrderPOST_PROCESS_MESSAGEPROCESSING_ORDERSHUTDOWN_PRIORITYintosenvirongetDEFAULT_AGGREGATE_TIMEOUTSHUTDOWN_SEND_TIMEOUTrr-r/r8r3r rrrGr) __classcell__)r"s@r#rrs)"2G !$ >CC!! C CRHH ). 3 3 3 3 3 3    3 3 3 VK %&& :] : : :'& :r$r)r1rrZloggingrdefence360agent.apir"defence360agent.contracts.messagesrrr!defence360agent.contracts.pluginsrr r defence360agent.utilsr r rLr5rrr$r#res ******  DCCCCCCC 8  ZZZZZmZZZZZr$defence360agent/plugins/__pycache__/analyst_cleanup_update.cpython-311.opt-1.pyc0000644000000000000000000002042400000000000024626 0ustar r_j$ddlZddlZddlmZddlmZddlmZddlmZddl m Z m Z ddl m Z ddlmZdd lmZdd lmZdd lmZdd lmZdd lmZejeZe de jZedgdZGddeZ dS)N)datetime) namedtuple)OperationalError) MessageSource)register_lock_fileScope)AnalystCleanupRequest)recurring_check)DAY) check_lock)AnalystCleanupAPI)remove_pub_key)IAIDTokenErrorzanalyst-cleanup-updateUpdateStatusRow) zendesk_id new_status updated_atcfeZdZdZdZededzfdZededzgfdZdZ dS) AnalystCleanupUpdatec K||_||_|ttdt dz t |j|_dS)NT)check_period_firstcheck_lock_period lock_file) _loop_sink create_taskr r r LOCK_FILE _update_task_task)selfloopsinks c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/analyst_cleanup_update.py create_sourcez"AnalystCleanupUpdate.create_sourcesv  %% ! O#'"%'#       ! ! # #   cVK|j|jd{VdSN)r cancel)r!s r$shutdownzAnalystCleanupUpdate.shutdown(s: jr&returnNc K|4d{V|j}||vr2td|d dddd{VdS||}|d}tj|ddd}dddd |d }|r||jkrtd |d |jd |d|dkrHtd|j dtj t|j d{Vt|||cdddd{VSdddd{VdS#1d{VswxYwYdS)NzTicket z" not found in Zendesk API responsestatusrZz+00:00pending completed)newsolvedclosed in_progresszUpdating ticket z status from 'z' to ''zRemoving SSH key for user ')rloggerwarningr fromisoformatreplacegetr-infousernameasyncio to_threadrr) old_requestnew_tickets_map semaphorerticket ticket_statusrrs r$_processzAnalystCleanupUpdate._process-s-& K& K& K& K& K& K& K& K$/J00LjLLL& K& K& K& K& K& K& K& K& K& K& K& K& K& K%Z0F"8,M!/|$,,S(;;J !%%c-//  KjK,>>> AzAA$+AA3=AAA ,,KKMk6JMMM"+& (<'z:zJJM& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& Ks+E C,E E%(E%rowsc`|D]*}|stj|j|j|j+dSr()r update_statusrrr)rErBs r$_update_db_statusesz(AnalystCleanupUpdate._update_db_statusesYsP  F  ! /!6#4f6G      r&cTK tj}|stddSna#t$rT}dt |vrtdntd|Yd}~dSd}~wwxYwd|D} tj|d{V}|st ddSd|Dtj d fd |D}tj |d{V}tj j|d{VdS#t$r(}td |Yd}~dSd}~wt $r(}td |Yd}~dSd}~wwxYw) a Gets all active and recently closed requests (for case if reopened). And asks all the requests status from zendesk API. Updates the state of the tickets in the database if changed. If any completed tickets, removes public key from relevant user. z4No relevant analyst cleanup requests found to updateNz no such tablez Database hasn't been updated yetz,Can't get data from analyst cleanup table: cg|] }|j S)r).0requests r$ z5AnalystCleanupUpdate._update_task..~sJJJgw)JJJr&z4Didn't get tickets info from imunifyAPI but expectedc:i|]}t|d|S)id)str)rLrBs r$ z5AnalystCleanupUpdate._update_task..s3.4F4L!!6r&c>g|]}|SrK)rD)rLr?r@r!rAs r$rNz5AnalystCleanupUpdate._update_task..s9 k?IFFr&zIAIDTokenError: z)Error updating analyst cleanup requests: )r get_all_relevant_requestsr6r;rrQerrorr get_ticketsr7r= Semaphoregatherr>rHr Exception) r!current_requestse zendesk_ids new_ticketstasksresultsr@rAs ` @@r$rz!AnalystCleanupUpdate._update_taskbsp %?AA  $  J       #a&&(( >???? F1FF FFFFF KJ9IJJJ  J 1 =k J JJJJJJJK J8CO )!,,I#3E $NE2222222G#D$rss""""""######;;;;;;MMMMMMMMGGGGGG111111,,,,,,777777HHHHHH888888999999  8 $ $  7 E E *AAA JJJJJ=JJJJJr&defence360agent/plugins/__pycache__/analyst_cleanup_update.cpython-311.pyc0000644000000000000000000002042400000000000023667 0ustar r_j$ddlZddlZddlmZddlmZddlmZddlmZddl m Z m Z ddl m Z ddlmZdd lmZdd lmZdd lmZdd lmZdd lmZejeZe de jZedgdZGddeZ dS)N)datetime) namedtuple)OperationalError) MessageSource)register_lock_fileScope)AnalystCleanupRequest)recurring_check)DAY) check_lock)AnalystCleanupAPI)remove_pub_key)IAIDTokenErrorzanalyst-cleanup-updateUpdateStatusRow) zendesk_id new_status updated_atcfeZdZdZdZededzfdZededzgfdZdZ dS) AnalystCleanupUpdatec K||_||_|ttdt dz t |j|_dS)NT)check_period_firstcheck_lock_period lock_file) _loop_sink create_taskr r r LOCK_FILE _update_task_task)selfloopsinks c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/analyst_cleanup_update.py create_sourcez"AnalystCleanupUpdate.create_sourcesv  %% ! O#'"%'#       ! ! # #   cVK|j|jd{VdSN)r cancel)r!s r$shutdownzAnalystCleanupUpdate.shutdown(s: jr&returnNc K|4d{V|j}||vr2td|d dddd{VdS||}|d}tj|ddd}dddd |d }|r||jkrtd |d |jd |d|dkrHtd|j dtj t|j d{Vt|||cdddd{VSdddd{VdS#1d{VswxYwYdS)NzTicket z" not found in Zendesk API responsestatusrZz+00:00pending completed)newsolvedclosed in_progresszUpdating ticket z status from 'z' to ''zRemoving SSH key for user ')rloggerwarningr fromisoformatreplacegetr-infousernameasyncio to_threadrr) old_requestnew_tickets_map semaphorerticket ticket_statusrrs r$_processzAnalystCleanupUpdate._process-s-& K& K& K& K& K& K& K& K$/J00LjLLL& K& K& K& K& K& K& K& K& K& K& K& K& K& K%Z0F"8,M!/|$,,S(;;J !%%c-//  KjK,>>> AzAA$+AA3=AAA ,,KKMk6JMMM"+& (<'z:zJJM& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& K& Ks+E C,E E%(E%rowsc`|D]*}|stj|j|j|j+dSr()r update_statusrrr)rErBs r$_update_db_statusesz(AnalystCleanupUpdate._update_db_statusesYsP  F  ! /!6#4f6G      r&cTK tj}|stddSna#t$rT}dt |vrtdntd|Yd}~dSd}~wwxYwd|D} tj|d{V}|st ddSd|Dtj d fd |D}tj |d{V}tj j|d{VdS#t$r(}td |Yd}~dSd}~wt $r(}td |Yd}~dSd}~wwxYw) a Gets all active and recently closed requests (for case if reopened). And asks all the requests status from zendesk API. Updates the state of the tickets in the database if changed. If any completed tickets, removes public key from relevant user. z4No relevant analyst cleanup requests found to updateNz no such tablez Database hasn't been updated yetz,Can't get data from analyst cleanup table: cg|] }|j S)r).0requests r$ z5AnalystCleanupUpdate._update_task..~sJJJgw)JJJr&z4Didn't get tickets info from imunifyAPI but expectedc:i|]}t|d|S)id)str)rLrBs r$ z5AnalystCleanupUpdate._update_task..s3.4F4L!!6r&c>g|]}|SrK)rD)rLr?r@r!rAs r$rNz5AnalystCleanupUpdate._update_task..s9 k?IFFr&zIAIDTokenError: z)Error updating analyst cleanup requests: )r get_all_relevant_requestsr6r;rrQerrorr get_ticketsr7r= Semaphoregatherr>rHr Exception) r!current_requestse zendesk_ids new_ticketstasksresultsr@rAs ` @@r$rz!AnalystCleanupUpdate._update_taskbsp %?AA  $  J       #a&&(( >???? F1FF FFFFF KJ9IJJJ  J 1 =k J JJJJJJJK J8CO )!,,I#3E $NE2222222G#D$rss""""""######;;;;;;MMMMMMMMGGGGGG111111,,,,,,777777HHHHHH888888999999  8 $ $  7 E E *AAA JJJJJ=JJJJJr&defence360agent/plugins/__pycache__/backup_info_sender.cpython-311.opt-1.pyc0000644000000000000000000001417200000000000023725 0ustar r_j  ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZmZdd lmZmZmZeeZeed Zd ZGd de ZdS)N) timedelta) getLogger)Union) MessageType) MessageSource)get_current_backendget_last_backup_timestamp) load_state save_state)Scoperecurring_checksafe_cancel_task)hoursceZdZdZejZdZdZe de e e fde fdZdde e e ffd Zd Zeed Zed d ZdZdS)BackupInfoSenderz.Send user backup statistics to CH periodicallycLK||_||_tj|_||_|j||_ |j| |_ dSN) _loop_sinkasyncioEvent _send_eventload_last_send_timestamp_last_send_timestamp create_task_recurring_check_data_to_send _check_task_recurring_send_stat_send_stat_task)selfloopsinks _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/backup_info_sender.py create_sourcezBackupInfoSender.create_sources  "=??$($A$A$C$C!:11  . . 0 0   $z55  % % ' '  c~K|j|jfD]}t|d{V|dSr)rr!rsave_last_send_timestamp)r"tasks r%shutdownzBackupInfoSender.shutdown's[%t';< ) )D"4(( ( ( ( ( ( ( ( ( %%'''''r' timestampreturncFt|ttfo|dkS)Nr) isinstanceintfloat)r,s r%is_valid_timestampz#BackupInfoSender.is_valid_timestamp,s)c5\22Dy1}Dr'Ntsc||jn|}||std|dSt dd|idS)NzInvalid timestamp: %srlast_send_timestamp)rr2loggerwarningr )r"r3r,s r%r)z)BackupInfoSender.save_last_send_timestamp0s`13D-- &&y11  NN2I > > > F%(=y'IJJJJJr'ctdd}||stdd}|S)Nrr5z(Invalid timestamp loaded, resetting to 0r)r getr2r6r7)r"r,s r%rz)BackupInfoSender.load_last_send_timestamp7sS122667LMM &&y11  NNE F F FIr'cKtj|jz tkr|jdSdSr)timer SEND_INTERVALrset)r"s r%rz.BackupInfoSender._recurring_check_data_to_send>sC 9;;2 2m C C   " " " " " D Cr'rcK|jd{V |d{Vn2#t$r%}td|Yd}~nd}~wwxYwt j|_|jdS#t j|_|jwxYw)Nz!Failed to collect backup info: %s) rwait_send_server_config Exceptionr6 exceptionr;rclear)r"es r%r z%BackupInfoSender._recurring_send_statCs##%%%%%%%%% %**,, , , , , , , , , E E E   @! D D D D D D D D E)- D %   " " $ $ $ $ $)- D %   " " $ $ $ $s,>B# A-A(#B#(A--B##3CcKtjttd{V}|j|d{VdS)N)backup_provider_typelast_backup_timestamp)r BackupInforr rprocess_message)r" confg_msgs r%r@z$BackupInfoSender._send_server_configOsq*!4!6!6(A(C(C"C"C"C"C"C"C   j((33333333333r'r)__name__ __module__ __qualname____doc__r IM360SCOPEr&r+ staticmethodrr0r1boolr2r)rr RECURRING_CHECK_INTERVALrr r@r'r%rrs88 KE    ((( EeCJ&7EDEEE\EKK5e+<KKKK_-..##/.#_Q % % %44444r'r)rr;datetimerloggingrtypingr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsr%defence360agent.subsys.backup_systemsrr 'defence360agent.subsys.persistent_stater r defence360agent.utilsr r rrKr6r0 total_secondsr<rSrrTr'r%r^sC ::::::;;;;;;KJJJJJJJJJJJJJJJJJ 8  IIB'''557788 >4>4>4>4>4}>4>4>4>4>4r'defence360agent/plugins/__pycache__/backup_info_sender.cpython-311.pyc0000644000000000000000000001417200000000000022766 0ustar r_j  ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZmZdd lmZmZmZeeZeed Zd ZGd de ZdS)N) timedelta) getLogger)Union) MessageType) MessageSource)get_current_backendget_last_backup_timestamp) load_state save_state)Scoperecurring_checksafe_cancel_task)hoursceZdZdZejZdZdZe de e e fde fdZdde e e ffd Zd Zeed Zed d ZdZdS)BackupInfoSenderz.Send user backup statistics to CH periodicallycLK||_||_tj|_||_|j||_ |j| |_ dSN) _loop_sinkasyncioEvent _send_eventload_last_send_timestamp_last_send_timestamp create_task_recurring_check_data_to_send _check_task_recurring_send_stat_send_stat_task)selfloopsinks _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/backup_info_sender.py create_sourcezBackupInfoSender.create_sources  "=??$($A$A$C$C!:11  . . 0 0   $z55  % % ' '  c~K|j|jfD]}t|d{V|dSr)rr!rsave_last_send_timestamp)r"tasks r%shutdownzBackupInfoSender.shutdown's[%t';< ) )D"4(( ( ( ( ( ( ( ( ( %%'''''r' timestampreturncFt|ttfo|dkS)Nr) isinstanceintfloat)r,s r%is_valid_timestampz#BackupInfoSender.is_valid_timestamp,s)c5\22Dy1}Dr'Ntsc||jn|}||std|dSt dd|idS)NzInvalid timestamp: %srlast_send_timestamp)rr2loggerwarningr )r"r3r,s r%r)z)BackupInfoSender.save_last_send_timestamp0s`13D-- &&y11  NN2I > > > F%(=y'IJJJJJr'ctdd}||stdd}|S)Nrr5z(Invalid timestamp loaded, resetting to 0r)r getr2r6r7)r"r,s r%rz)BackupInfoSender.load_last_send_timestamp7sS122667LMM &&y11  NNE F F FIr'cKtj|jz tkr|jdSdSr)timer SEND_INTERVALrset)r"s r%rz.BackupInfoSender._recurring_check_data_to_send>sC 9;;2 2m C C   " " " " " D Cr'rcK|jd{V |d{Vn2#t$r%}td|Yd}~nd}~wwxYwt j|_|jdS#t j|_|jwxYw)Nz!Failed to collect backup info: %s) rwait_send_server_config Exceptionr6 exceptionr;rclear)r"es r%r z%BackupInfoSender._recurring_send_statCs##%%%%%%%%% %**,, , , , , , , , , E E E   @! D D D D D D D D E)- D %   " " $ $ $ $ $)- D %   " " $ $ $ $s,>B# A-A(#B#(A--B##3CcKtjttd{V}|j|d{VdS)N)backup_provider_typelast_backup_timestamp)r BackupInforr rprocess_message)r" confg_msgs r%r@z$BackupInfoSender._send_server_configOsq*!4!6!6(A(C(C"C"C"C"C"C"C   j((33333333333r'r)__name__ __module__ __qualname____doc__r IM360SCOPEr&r+ staticmethodrr0r1boolr2r)rr RECURRING_CHECK_INTERVALrr r@r'r%rrs88 KE    ((( EeCJ&7EDEEE\EKK5e+<KKKK_-..##/.#_Q % % %44444r'r)rr;datetimerloggingrtypingr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsr%defence360agent.subsys.backup_systemsrr 'defence360agent.subsys.persistent_stater r defence360agent.utilsr r rrKr6r0 total_secondsr<rSrrTr'r%r^sC ::::::;;;;;;KJJJJJJJJJJJJJJJJJ 8  IIB'''557788 >4>4>4>4>4}>4>4>4>4>4r'defence360agent/plugins/__pycache__/cagefs.cpython-311.opt-1.pyc0000644000000000000000000001711700000000000021337 0ustar r_jdZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZmZddlmZd Zd ZejeZGd d e ZdS) a Goal: Invoke /usr/sbin/cagefsctl --update-etc /usr/sbin/cagefsctl --force-update-etc asynchronously. As far production scale `cagefsctl --force-update-etc` tends last for too long, e.g. - # time cagefsctl --force-update-etc Updating users ... Updating user user523 ... Updating user user804 ... ... Updating user user269 ... Updating user user116 ... Updating user user121 ... Updating user user117 ... real 2m44.454s user 0m26.233s sys 0m19.972s N)Optional) inactivity) MessageType) MessageSinkexpect) load_state save_state)timefunz/usr/sbin/cagefsctlz --wait-lockceZdZdejfdZdZeej dZ dZ e e jdeefdZed Zd S) CageFSloopcK||_tj|_t ddd|_|j||_ dS)Nr last_force_update_tsr) _loopasyncioQueue_queuerget_last_force_update_ts create_task _consumer_consumer_task)selfr s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/cagefs.py create_sinkzCageFS.create_sink,si moo %/%9%9%=%= "A& & "#j44T^^5E5EFFcK|j|jd{V|jr2td|jt dd|jidS)Nz%d item(s) were not consumedr r)rcancelrqsizeloggerwarningr r)rs rshutdownzCageFS.shutdown4s ""$$$!!!!!!!! ;     P NN94;;L;L;N;N O O O -t/IJ     rcK|d}t|dd}|||jr|j|dSdS)Nconfusername)getattrmodified_sincerr put_nowait)rmessageconfigr%s r put_to_queuezCageFS.put_to_queue?si6:t44  6#8#8  &$ $  K " "8 , , , , , rcNK |jd{V}tjt sF|h} ||j-#tj $rYnwxYwtj d5|D]}| |d{V dddn #1swxYwYn<#tj$rYdSt$rt dYwxYw#)z :raise never: TNcagefszSomething went wrong)rrospathexists_CAGEFSCTL_TOOLadd get_nowaitr QueueEmptyrtracktask _commitconfigCancelledError Exceptionr exception)rcommitconfig_usernameuniqr%s rrzCageFS._consumerMs  .2koo.?.?(?(?(?(?(?(?%w~~o66..;!7!7!9!9:::;)D %**844;;$(;;"00::::::::::;;;;;;;;;;;;;;;;)        !7888  / s`AC) C) .A;;B  C) B  "C)/!C C)C!!C)$C!%C))D";#D"!D")logr%cNK|rttd|g}nttdg}tj} tj|t jt jt jddd{V}||tj |j }||tj |j }tj||d{V|d{V\}}|d{V} | t"ddS| r t"d|| ||dSt"d|| | ||_dSdS#tj$rt"d |wxYw) zJ :raise asyncio.CancelledError: :raise Exception: z --update-etcz--force-update-etcF)stdinstdoutstderrstart_new_sessionNz+logic error: process has not terminated yetz,%r failed with rc [%s], stdout=%s, stderr=%sz%r succeeded with rc [%s]z"%r is terminated by CancelledError)r1 _WAIT_LOCKtimercreate_subprocess_exec subprocessDEVNULLPIPE _passthru_logloggingDEBUGr@WARNrAgather communicatewaitr errorinforr8r!) rr%cmd started_atprocfuture1future2outerrrcs rr7zCageFS._commitconfigns  F"JICC"J0DECY[[ " < 7 (!!#(D((gmT[IIG((glDKHHG.'22 2 2 2 2 2 2 2!--////////HCyy{{""""""B z JKKKKK < B  7bAAA#1;D...$#!%    NN? E E E  s CE88,F$cK |d{V}|sdSt|d||<)NTz%r: %r)readliner r=)rRloglevel streamreaderlines rrIzCageFS._passthru_logsX 6%..00000000D  JJx3 5 5 5  6rN)__name__ __module__ __qualname__rAbstractEventLooprr"rr ConfigUpdater+rr r rQrstrr7 staticmethodrIrrr r +sGg&?GGGG     VK $%% - -&% -B W.ros.  ******::::::AAAAAAAAJJJJJJJJ))))))'   8 $ $z6z6z6z6z6[z6z6z6z6z6rdefence360agent/plugins/__pycache__/cagefs.cpython-311.pyc0000644000000000000000000001711700000000000020400 0ustar r_jdZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZmZddlmZd Zd ZejeZGd d e ZdS) a Goal: Invoke /usr/sbin/cagefsctl --update-etc /usr/sbin/cagefsctl --force-update-etc asynchronously. As far production scale `cagefsctl --force-update-etc` tends last for too long, e.g. - # time cagefsctl --force-update-etc Updating users ... Updating user user523 ... Updating user user804 ... ... Updating user user269 ... Updating user user116 ... Updating user user121 ... Updating user user117 ... real 2m44.454s user 0m26.233s sys 0m19.972s N)Optional) inactivity) MessageType) MessageSinkexpect) load_state save_state)timefunz/usr/sbin/cagefsctlz --wait-lockceZdZdejfdZdZeej dZ dZ e e jdeefdZed Zd S) CageFSloopcK||_tj|_t ddd|_|j||_ dS)Nr last_force_update_tsr) _loopasyncioQueue_queuerget_last_force_update_ts create_task _consumer_consumer_task)selfr s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/cagefs.py create_sinkzCageFS.create_sink,si moo %/%9%9%=%= "A& & "#j44T^^5E5EFFcK|j|jd{V|jr2td|jt dd|jidS)Nz%d item(s) were not consumedr r)rcancelrqsizeloggerwarningr r)rs rshutdownzCageFS.shutdown4s ""$$$!!!!!!!! ;     P NN94;;L;L;N;N O O O -t/IJ     rcK|d}t|dd}|||jr|j|dSdS)Nconfusername)getattrmodified_sincerr put_nowait)rmessageconfigr%s r put_to_queuezCageFS.put_to_queue?si6:t44  6#8#8  &$ $  K " "8 , , , , , rcNK |jd{V}tjt sF|h} ||j-#tj $rYnwxYwtj d5|D]}| |d{V dddn #1swxYwYn<#tj$rYdSt$rt dYwxYw#)z :raise never: TNcagefszSomething went wrong)rrospathexists_CAGEFSCTL_TOOLadd get_nowaitr QueueEmptyrtracktask _commitconfigCancelledError Exceptionr exception)rcommitconfig_usernameuniqr%s rrzCageFS._consumerMs  .2koo.?.?(?(?(?(?(?(?%w~~o66..;!7!7!9!9:::;)D %**844;;$(;;"00::::::::::;;;;;;;;;;;;;;;;)        !7888  / s`AC) C) .A;;B  C) B  "C)/!C C)C!!C)$C!%C))D";#D"!D")logr%cNK|rttd|g}nttdg}tj} tj|t jt jt jddd{V}||tj |j }||tj |j }tj||d{V|d{V\}}|d{V} | t"ddS| r t"d|| ||dSt"d|| | ||_dSdS#tj$rt"d |wxYw) zJ :raise asyncio.CancelledError: :raise Exception: z --update-etcz--force-update-etcF)stdinstdoutstderrstart_new_sessionNz+logic error: process has not terminated yetz,%r failed with rc [%s], stdout=%s, stderr=%sz%r succeeded with rc [%s]z"%r is terminated by CancelledError)r1 _WAIT_LOCKtimercreate_subprocess_exec subprocessDEVNULLPIPE _passthru_logloggingDEBUGr@WARNrAgather communicatewaitr errorinforr8r!) rr%cmd started_atprocfuture1future2outerrrcs rr7zCageFS._commitconfigns  F"JICC"J0DECY[[ " < 7 (!!#(D((gmT[IIG((glDKHHG.'22 2 2 2 2 2 2 2!--////////HCyy{{""""""B z JKKKKK < B  7bAAA#1;D...$#!%    NN? E E E  s CE88,F$cK |d{V}|sdSt|d||<)NTz%r: %r)readliner r=)rRloglevel streamreaderlines rrIzCageFS._passthru_logsX 6%..00000000D  JJx3 5 5 5  6rN)__name__ __module__ __qualname__rAbstractEventLooprr"rr ConfigUpdater+rr r rQrstrr7 staticmethodrIrrr r +sGg&?GGGG     VK $%% - -&% -B W.ros.  ******::::::AAAAAAAAJJJJJJJJ))))))'   8 $ $z6z6z6z6z6[z6z6z6z6z6rdefence360agent/plugins/__pycache__/checkpoint.cpython-311.opt-1.pyc0000644000000000000000000000420600000000000022231 0ustar r_jFddlmZddlmZddlmZGddeZdS)) MessageSink)db)recurring_checkc6eZdZdZdZeeddZdZdZdZ dS) CheckpointzU Checkpoint imunify360.db periodically to limit unexpected WAL file growing. iQ)checkpoint_periodrc0||_||_d|_dSN)_checkpoint_period_db_task)selfrrs W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/checkpoint.py__init__zCheckpoint.__init__ s"3 cK||_|jt|j|j|_dSr )_loop create_taskrr _checkpointr )rloops r create_sinkzCheckpoint.create_sinksN Z++ F 4OD3 4 4T5E F F H H   rcK|jdc}|_||rdS||d{VdSr )r cancelledcancel)rtasks rshutdownzCheckpoint.shutdownsP:tdj <4>>++< F  rc>K|jddS)NzPRAGMA wal_checkpoint(TRUNCATE))r execute_sql)rs rrzCheckpoint._checkpoint s% >?????rN) __name__ __module__ __qualname____doc__ONE_DAYrrrrrrrrrsuG,3    @@@@@rrN)!defence360agent.contracts.pluginsrdefence360agent.model.instancerdefence360agent.utilsrrr$rrr(sy999999------111111@@@@@@@@@@rdefence360agent/plugins/__pycache__/checkpoint.cpython-311.pyc0000644000000000000000000000420600000000000021272 0ustar r_jFddlmZddlmZddlmZGddeZdS)) MessageSink)db)recurring_checkc6eZdZdZdZeeddZdZdZdZ dS) CheckpointzU Checkpoint imunify360.db periodically to limit unexpected WAL file growing. iQ)checkpoint_periodrc0||_||_d|_dSN)_checkpoint_period_db_task)selfrrs W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/checkpoint.py__init__zCheckpoint.__init__ s"3 cK||_|jt|j|j|_dSr )_loop create_taskrr _checkpointr )rloops r create_sinkzCheckpoint.create_sinksN Z++ F 4OD3 4 4T5E F F H H   rcK|jdc}|_||rdS||d{VdSr )r cancelledcancel)rtasks rshutdownzCheckpoint.shutdownsP:tdj <4>>++< F  rc>K|jddS)NzPRAGMA wal_checkpoint(TRUNCATE))r execute_sql)rs rrzCheckpoint._checkpoint s% >?????rN) __name__ __module__ __qualname____doc__ONE_DAYrrrrrrrrrsuG,3    @@@@@rrN)!defence360agent.contracts.pluginsrdefence360agent.model.instancerdefence360agent.utilsrrr$rrr(sy999999------111111@@@@@@@@@@rdefence360agent/plugins/__pycache__/client.cpython-311.opt-1.pyc0000644000000000000000000006737700000000000021402 0ustar r_jQLddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z m Z mZmZddlmZddlmZddlmZmZmZmZddlmZmZddlmZmZdd lm Z m!Z!dd l"m#Z#m$Z$dd l%m&Z&dd l'm(Z(m)Z)m*Z*m+Z+dd l,m-Z-ej.e/Z0e#Z1e#Z2e#Z3GddZ4Gdde4eZ5dS)N) Generator)APIErrorAPIErrorTooManyRequests APITokenError send_message)license)Core)GeneralMetricsMessage MessageList MessageType) MessageSinkexpect) delivery_ack feature_flags)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)Gen publisher)PersistentMessagesQueue)log_future_errorsrecurring_checksafe_cancel_taskScope)ServerJSONEncoderc&eZdZdZeejddZdZ dZ dZ dZ dZ d Zfd Zd ejfd Zee d ZdefdZdefdZdZdedefdZd%dZdZedZe j!de"e#j$ddffdZ%e&e'j(deddfdZ)ee dZ*de+fdZ,eddZ-dede.fdZ/de.defd Z0d!Z1d"Z2d#Z3d%d$Z4xZ5S)&SendToServerClientaSend messages to server. * process Reportable messages; * add them to a pending messages list; * send all pending messages to server when list is full (contains _PENDING_MESSAGES_LIMIT items or more) or when the oldest pending message has waited the max send delay (0 unless batching is enabled via the feature flag); * send all pending messages on plugin shutdown. IMUNIFYAV_MESSAGES_COUNT_TO_SENDgmessage_send_batching<i,2cHtj|i|i|_dSN)super__init___unsent_metrics)selfargskwargs __class__s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/client.pyr'zSendToServerClient.__init__Is-$)&)))!loopcK||_t|_tj|_tj|_tj|_d|_ | | |_ | | |_| ||_dSr%)_loopr_pendingasyncioEvent _try_sendLock_lock_shutting_down_flush_deadline create_task_report_metrics _metrics_task_send _sender_task_invoke_send_message_invoke_send_message_task)r)r/s r- create_sinkzSendToServerClient.create_sinkMs /11  \^^ %moo#!--d.B.B.D.DEE ,,TZZ\\::)-)9)9  % % ' '* * &&&r.c>K|d{VdSr%) _emit_metricsr)s r-r;z"SendToServerClient._report_metricsZs0  """""""""""r.returnc|jtjt jdS)N)zagent.persistent_queue.evictedzagent.msg_status.droppedz!agent.send.method_missing_dropped)r2 pop_evictedr pop_droppedrpop_method_missing_droppedrDs r-_collect_metricsz#SendToServerClient._collect_metrics^s:.2m.G.G.I.I(1(=(?(?799    r.cr|j|jjtjdS)N)zagent.persistent_queue.sizez#agent.persistent_queue.storage_sizezagent.msg_status.queue_size)r2qsize storage_sizer queue_depthrDs r-_collect_gaugesz"SendToServerClient._collect_gaugesgs6+/=+>+>+@+@37=3M+4+@+B+B   r.cK|j}i|_|}ttsdS|D]#\}}|r||d|z||<$d} i||}td|D}tj|d<tj j |d<|j 4d{V| |d{V}dddd{Vn#1d{VswxYwYnH#tj$r ||_t $r%}t"d|Yd}~nd}~wwxYw|s ||_dSdS)NrFcg|] \}}||d S))namevalue).0rRrSs r- z4SendToServerClient._emit_metrics..s4#e"E22r. timestamp message_idz"Failed to deliver loss metrics: %r)r(rJrritemsgetrOr timeuuiduuid4hexr7_send_metrics_directr3CancelledError Exceptionloggerwarning) r)metrics collectedrRrSsentpayloadmessageexcs r-rCz SendToServerClient._emit_metricsns&!))++ 9::  F$??,, = =KD% = ' D! 4 4u <  F <;D$8$8$:$:;G$'.}}G $(9;;GK $(JLL$4GL ! z @ @ @ @ @ @ @ @!66w???????? @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @%   $+D  F F F NN? E E E E E E E E F +#*D  + +s=6BD77D%D7% D//D72D/3D77 E<E77E<rhcK|5}|tj||fgd{Vdddn #1swxYwYdS)NT)_get_api send_messagesr[_encode_data_to_put_in_queuer)rhapis r-r_z'SendToServerClient._send_metrics_directs ]]__ ##)++t@@IIJK                      tsAA&&A*-A*NcxK|j tj||jd{Vnh#tj$rVtd|j|j st|j d{VYnwxYw|j j dkrrtd|j j |j td|j dSdS)a~ When shutdown begins it signals any in-flight HTTP sends to abort immediately (via _shutting_down event), then gives 50 seconds to finish the stop() sequence. If stop() isn't done in 50 seconds it force-cancels the sender task. Finally, any messages still in the buffer are flushed to persistent storage so nothing is lost. Nz5Timeout (%ds) sending messages to server on shutdown.rz&Save %s messages to persistent storagezStored queue %r)r8setr3wait_forstop_SHUTDOWN_SEND_TIMEOUT TimeoutErrorrberrorr> cancelledrr2 buffer_sizercpush_buffer_to_storagerLrDs r-shutdownzSendToServerClient.shutdownsJ !!! :"499;;0KLL L L L L L L L L# : : : LLG+   $..00 :&t'8999999999 : = $q ( ( NN8 )    M 0 0 2 2 2 NN,dm.A.A.C.C D D D D D ) (s2AA"B54B5cKtdt|jd{V|jt|jd{Vtd|j4d{Vtdt|jd{V|j| d{Vdddd{Vn#1d{VswxYwY|jktj tj 5tj||jd{VddddS#1swxYwYdSdS)aq Stop sending. 1. wait for the lock being available i.e., while _sender_task finishes the current round of sending message (if it takes too long, then the timeout in shutdown() is triggered 2. once the sending round complete (we got the lock), cancel the next iteration of the _sender_task (it exits) 3. send _pending messages (again, if it takes too long, the timeout in shutdown() is triggered and the coroutine is cancelled That method makes sure that the coroutine that was started in it has ended. It excludes a situation when: -> The result of a coroutine that started BEFORE shutdown() is started. -> And the process of sending messages from _pending is interrupted because of it z2SendToServer.stop cancel _invoke_send_message_taskNzSendToServer.stop wait lockz4SendToServer.stop lock acquired, cancel _sender_task)rbinforr@r<r7r>r8clear_send_pending_messages contextlibsuppressr3rurrrC_METRICS_FLUSH_TIMEOUTrDs r-rszSendToServerClient.stops{0  HIIIt=>>>>>>>>>   )"4#566 6 6 6 6 6 6 6 1222: 0 0 0 0 0 0 0 0 KKN O O O"4#455 5 5 5 5 5 5 5   % % ' ' '--// / / / / / / / 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0   )$W%9::  &&&(($*E                   * )s%?A(C99 DD/3E//E36E3c,|tj|tj|tj|Sr%)set_product_namer LicenseCLNget_product_name set_server_id get_server_id set_license get_token)ros r-_set_api_attrsz!SendToServerClient._set_api_attrssn W/@@BBCCC ',::<<=== *4466777 r.c#*Ktjd}tjd5}t jtj ||}| |VddddS#1swxYwYdS)NIMUNIFYAV_API_BASE) max_workers)executor) osenvironrZ concurrentfuturesThreadPoolExecutorrSendMessageAPIr VERSIONr)r)base_urlrros r-rkzSendToServerClient._get_apis:>>"677   2 2q 2 A A +X- hC%%c** * * *  + + + + + + + + + + + + + + + + + +s9BB B c@Kd|vrtj|d<d|vrtjj|d<|j|||jtj |tddS)NrWrXz agent-queuedstage) r[r\r]r^r2putrmr5rqrreport_reporter_gen_queued)r)rhs r-send_to_serverz!SendToServerClient.send_to_servers g % %#'9;;GK w & &$(JLL$4GL ! $;;GDDEEE "6nMMMMMMr.c<K|jdSr%)r5rqrDs r-r?z'SendToServerClient._invoke_send_messages  r.ctj|jr=tj|jD]#} t |cS#t $rY wxYw|jSr%)rr_BATCHING_FLAG get_paramsfloat ValueError_MAX_SEND_DELAY)r)rSs r-_max_send_delayz"SendToServerClient._max_send_delaysu  #D$7 8 8 &1$2EFF   <<'''!D##sA AArcK|j |jd{Vntd|j|jz }t jtj 5tj |j|d{Vdddn #1swxYwY|j |j }|dkr d|_dS|j3|j|z|_||jkr$|j|jkrdSd|_t dd}|j4d{Vt d |d{Vn8#tj$r&}t d|}Yd}~nd}~wwxYwdddd{Vn#1d{VswxYwYt d|r|dS)NrzSendToServer._send wait lockz SendToServer._send lock acquiredz&SendToServer._send cancelled unlockingz SendToServer._send lock released)r9r5waitmaxr1r[rrr3rurrr}r2rLr_PENDING_MESSAGES_LIMITrbr|r7r~r`)r)timeoutrLneed_to_canceles r-r=zSendToServerClient._send!s'   '.%%'' ' ' ' ' ' ' ' '!T1DJOO4E4EEFFG$W%9:: G G&t~':':'<'z6SendToServerClient._decode_message..Ls#EEEAf1r.)rloadsrZr updaterYr )r)rhrrs r-_decode_messagez"SendToServerClient._decode_messageHspz'"" 88F   d6l++C JJEEEEE F F FJt}}r.c|dddz|d<||}|7|j|||jdS|j||dS)Napi_retries_countr)rW)rZrmr2rryupdate_message)r)rXrWrhencodeds r-_persist_failedz"SendToServerClient._persist_failedPs'.{{3F'J'JQ'N#$33G<<   M  g  ; ; ; M 0 0 2 2 2 2 2 M ( (W = = = = =r.cKtj||}|dtj|j} tj||htjd{V\}}n<#tj$r*||wxYw|D]}t|d{V||vr| dSdS)zRace the HTTP send against the shutdown signal. Returns True on success, raises on API error, or returns False if shutdown interrupted the send. c6t|tjSr%)rrbdebug)tasks r-z6SendToServerClient._send_one_message..cs*4>>r.) return_whenNTF) r3 ensure_futureradd_done_callbackr8rFIRST_COMPLETEDr`cancelrresult)r)rorh send_task shutdown_taskdone pending_tasksrs r-_send_one_messagez$SendToServerClient._send_one_messageZsW )#*:*:7*C*CDD ## > >    -d.A.F.F.H.HII  (/ M*#3)))###### D--%           " " "  " ) )D"4(( ( ( ( ( ( ( ( (         4us ,+B9CcK|jrtddS||}|d|dd} t j|td| ||d{V}|std dS|drit j|td t d |tj |d||j|gd S#t"t$f$r>}td ||||||Yd}~dSd}~wt($r>}td ||||||Yd}~dSd}~wwxYw)zDeliver one message and return (stop, failed); message_id is None for a fresh memory-only message, set for a stored row.z4Shutdown signal received, keeping remaining messages)TFmethodrX)rrXz agent-sendingrNz@Shutdown signal received during send, keeping remaining messagesz agent-sentzmessage sent %s)FFz'Failed to send message %s to server: %s)TT)FT)r8is_setrbrcrrZrr_reporter_gen_sendingr_reporter_gen_sentr|rregistryconfirmr2deleterrrr) r)rorXrW message_bytesrhmsg_inforfris r- _try_send_onez SendToServerClient._try_send_onezsG   % % ' '  NNF   ;&&}55kk(++!++l33  !   .o    //W========D #2#{! I /| -x888%--gkk,.G.GHHH% $$j\222<'7    NN98S     Y @ @ @:::::    NN98S     Y @ @ @;;;;;  s,9AEBEG403F)) G463G//G4cK|5}|j ddddSt|jd|jDz}t dt|d}d} |D]R\}}}| ||||d{V\}} |r| sn&|dz }| r|dz }|r|t||z z }nSd||dD} | r3|j | |j nO#d||dD} | r4|j | |j wwxYwt d|ddddS#1swxYwYdS)Ncg|] \}}d||f Sr%rT)rUrWrs r-rVz=SendToServerClient._send_pending_messages..s3999,I}y-0999r.zSending %s messagesrrc"g|] \}}}|||f Sr%rT)rUmidtsmbs r-rVz=SendToServerClient._send_pending_messages..s/%%%#R{H"{{r.z Unsuccessful to send %s messages) rk server_idrr2 peek_stored drain_bufferrbr|lenrput_manyry) r)robatch failure_count processedrXrWrrsfailedunattempted_freshs r-r~z)SendToServerClient._send_pending_messagess ]]__% K}$% K% K% K% K% K% K% K% K2244559904 0J0J0L0L999E KK-s5zz : : :MI ;F>)rEN)6__name__ __module__ __qualname____doc__intrrrZrrr_SEND_MESSAGE_RECURRING_TIME_METRICS_REPORT_INTERVALrtrr'r3AbstractEventLooprArr;dictrJrOrCr boolr_rzrs staticmethodrrcontextmanagerrrrrkrr Reportablerr?rrr=bytesrmrrrrr~ __classcell__)r,s@r-rr2s77"c 92>>O,N#% %"""""  g&?     _-..##/.# $         ,+,+,+\'d!E!E!E!EF...`\ +)L$?t$KL++++ VK "## NG N N N N$# N_12232$$$$$_Q ! ! !DUu>>>@///b&K&K&K&K&K&K&K&Kr.rc0eZdZejZdZdedefdZ dS) SendToServerirhrEcK|5}|j ddddS||d{Vdddn #1swxYwYdS)NFT)rkrrrns r-r_z!SendToServer._send_metrics_directs ]]__ ,}$ , , , , , , , ,""7++ + + + + + + + , , , , , , , , , , , , , , ,ts AAAAN) rrrrAVSCOPESHUTDOWN_PRIORITYr rr_rTr.r-rrsC HE'dr.r)6r3concurrent.futuresrrrloggingrr[r\typingrdefence360agent.api.serverrrrrdefence360agent.contractsr defence360agent.contracts.configr "defence360agent.contracts.messagesr r r r !defence360agent.contracts.pluginsrrdefence360agent.internalsrr'defence360agent.internals.feature_flagsrr2defence360agent.internals.message_status_publisherrr,defence360agent.internals.persistent_messagerdefence360agent.utilsrrrrdefence360agent.utils.jsonr getLoggerrrbrrrrrrTr.r-rs   .-----111111 BAAAAAAAAAAAAAAANMMMMMMM 988888  8 $ $suuSUU_K_K_K_K_K_K_K_KD     %{     r.defence360agent/plugins/__pycache__/client.cpython-311.pyc0000644000000000000000000006737700000000000020443 0ustar r_jQLddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z m Z mZmZddlmZddlmZddlmZmZmZmZddlmZmZddlmZmZdd lm Z m!Z!dd l"m#Z#m$Z$dd l%m&Z&dd l'm(Z(m)Z)m*Z*m+Z+dd l,m-Z-ej.e/Z0e#Z1e#Z2e#Z3GddZ4Gdde4eZ5dS)N) Generator)APIErrorAPIErrorTooManyRequests APITokenError send_message)license)Core)GeneralMetricsMessage MessageList MessageType) MessageSinkexpect) delivery_ack feature_flags)MESSAGE_LOSS_OBSERVABILITY_FLAG is_enabled)Gen publisher)PersistentMessagesQueue)log_future_errorsrecurring_checksafe_cancel_taskScope)ServerJSONEncoderc&eZdZdZeejddZdZ dZ dZ dZ dZ d Zfd Zd ejfd Zee d ZdefdZdefdZdZdedefdZd%dZdZedZe j!de"e#j$ddffdZ%e&e'j(deddfdZ)ee dZ*de+fdZ,eddZ-dede.fdZ/de.defd Z0d!Z1d"Z2d#Z3d%d$Z4xZ5S)&SendToServerClientaSend messages to server. * process Reportable messages; * add them to a pending messages list; * send all pending messages to server when list is full (contains _PENDING_MESSAGES_LIMIT items or more) or when the oldest pending message has waited the max send delay (0 unless batching is enabled via the feature flag); * send all pending messages on plugin shutdown. IMUNIFYAV_MESSAGES_COUNT_TO_SENDgmessage_send_batching<i,2cHtj|i|i|_dSN)super__init___unsent_metrics)selfargskwargs __class__s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/client.pyr'zSendToServerClient.__init__Is-$)&)))!loopcK||_t|_tj|_tj|_tj|_d|_ | | |_ | | |_| ||_dSr%)_loopr_pendingasyncioEvent _try_sendLock_lock_shutting_down_flush_deadline create_task_report_metrics _metrics_task_send _sender_task_invoke_send_message_invoke_send_message_task)r)r/s r- create_sinkzSendToServerClient.create_sinkMs /11  \^^ %moo#!--d.B.B.D.DEE ,,TZZ\\::)-)9)9  % % ' '* * &&&r.c>K|d{VdSr%) _emit_metricsr)s r-r;z"SendToServerClient._report_metricsZs0  """""""""""r.returnc|jtjt jdS)N)zagent.persistent_queue.evictedzagent.msg_status.droppedz!agent.send.method_missing_dropped)r2 pop_evictedr pop_droppedrpop_method_missing_droppedrDs r-_collect_metricsz#SendToServerClient._collect_metrics^s:.2m.G.G.I.I(1(=(?(?799    r.cr|j|jjtjdS)N)zagent.persistent_queue.sizez#agent.persistent_queue.storage_sizezagent.msg_status.queue_size)r2qsize storage_sizer queue_depthrDs r-_collect_gaugesz"SendToServerClient._collect_gaugesgs6+/=+>+>+@+@37=3M+4+@+B+B   r.cK|j}i|_|}ttsdS|D]#\}}|r||d|z||<$d} i||}td|D}tj|d<tj j |d<|j 4d{V| |d{V}dddd{Vn#1d{VswxYwYnH#tj$r ||_t $r%}t"d|Yd}~nd}~wwxYw|s ||_dSdS)NrFcg|] \}}||d S))namevalue).0rRrSs r- z4SendToServerClient._emit_metrics..s4#e"E22r. timestamp message_idz"Failed to deliver loss metrics: %r)r(rJrritemsgetrOr timeuuiduuid4hexr7_send_metrics_directr3CancelledError Exceptionloggerwarning) r)metrics collectedrRrSsentpayloadmessageexcs r-rCz SendToServerClient._emit_metricsns&!))++ 9::  F$??,, = =KD% = ' D! 4 4u <  F <;D$8$8$:$:;G$'.}}G $(9;;GK $(JLL$4GL ! z @ @ @ @ @ @ @ @!66w???????? @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @%   $+D  F F F NN? E E E E E E E E F +#*D  + +s=6BD77D%D7% D//D72D/3D77 E<E77E<rhcK|5}|tj||fgd{Vdddn #1swxYwYdS)NT)_get_api send_messagesr[_encode_data_to_put_in_queuer)rhapis r-r_z'SendToServerClient._send_metrics_directs ]]__ ##)++t@@IIJK                      tsAA&&A*-A*NcxK|j tj||jd{Vnh#tj$rVtd|j|j st|j d{VYnwxYw|j j dkrrtd|j j |j td|j dSdS)a~ When shutdown begins it signals any in-flight HTTP sends to abort immediately (via _shutting_down event), then gives 50 seconds to finish the stop() sequence. If stop() isn't done in 50 seconds it force-cancels the sender task. Finally, any messages still in the buffer are flushed to persistent storage so nothing is lost. Nz5Timeout (%ds) sending messages to server on shutdown.rz&Save %s messages to persistent storagezStored queue %r)r8setr3wait_forstop_SHUTDOWN_SEND_TIMEOUT TimeoutErrorrberrorr> cancelledrr2 buffer_sizercpush_buffer_to_storagerLrDs r-shutdownzSendToServerClient.shutdownsJ !!! :"499;;0KLL L L L L L L L L# : : : LLG+   $..00 :&t'8999999999 : = $q ( ( NN8 )    M 0 0 2 2 2 NN,dm.A.A.C.C D D D D D ) (s2AA"B54B5cKtdt|jd{V|jt|jd{Vtd|j4d{Vtdt|jd{V|j| d{Vdddd{Vn#1d{VswxYwY|jktj tj 5tj||jd{VddddS#1swxYwYdSdS)aq Stop sending. 1. wait for the lock being available i.e., while _sender_task finishes the current round of sending message (if it takes too long, then the timeout in shutdown() is triggered 2. once the sending round complete (we got the lock), cancel the next iteration of the _sender_task (it exits) 3. send _pending messages (again, if it takes too long, the timeout in shutdown() is triggered and the coroutine is cancelled That method makes sure that the coroutine that was started in it has ended. It excludes a situation when: -> The result of a coroutine that started BEFORE shutdown() is started. -> And the process of sending messages from _pending is interrupted because of it z2SendToServer.stop cancel _invoke_send_message_taskNzSendToServer.stop wait lockz4SendToServer.stop lock acquired, cancel _sender_task)rbinforr@r<r7r>r8clear_send_pending_messages contextlibsuppressr3rurrrC_METRICS_FLUSH_TIMEOUTrDs r-rszSendToServerClient.stops{0  HIIIt=>>>>>>>>>   )"4#566 6 6 6 6 6 6 6 1222: 0 0 0 0 0 0 0 0 KKN O O O"4#455 5 5 5 5 5 5 5   % % ' ' '--// / / / / / / / 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0   )$W%9::  &&&(($*E                   * )s%?A(C99 DD/3E//E36E3c,|tj|tj|tj|Sr%)set_product_namer LicenseCLNget_product_name set_server_id get_server_id set_license get_token)ros r-_set_api_attrsz!SendToServerClient._set_api_attrssn W/@@BBCCC ',::<<=== *4466777 r.c#*Ktjd}tjd5}t jtj ||}| |VddddS#1swxYwYdS)NIMUNIFYAV_API_BASE) max_workers)executor) osenvironrZ concurrentfuturesThreadPoolExecutorrSendMessageAPIr VERSIONr)r)base_urlrros r-rkzSendToServerClient._get_apis:>>"677   2 2q 2 A A +X- hC%%c** * * *  + + + + + + + + + + + + + + + + + +s9BB B c@Kd|vrtj|d<d|vrtjj|d<|j|||jtj |tddS)NrWrXz agent-queuedstage) r[r\r]r^r2putrmr5rqrreport_reporter_gen_queued)r)rhs r-send_to_serverz!SendToServerClient.send_to_servers g % %#'9;;GK w & &$(JLL$4GL ! $;;GDDEEE "6nMMMMMMr.c<K|jdSr%)r5rqrDs r-r?z'SendToServerClient._invoke_send_messages  r.ctj|jr=tj|jD]#} t |cS#t $rY wxYw|jSr%)rr_BATCHING_FLAG get_paramsfloat ValueError_MAX_SEND_DELAY)r)rSs r-_max_send_delayz"SendToServerClient._max_send_delaysu  #D$7 8 8 &1$2EFF   <<'''!D##sA AArcK|j |jd{Vntd|j|jz }t jtj 5tj |j|d{Vdddn #1swxYwY|j |j }|dkr d|_dS|j3|j|z|_||jkr$|j|jkrdSd|_t dd}|j4d{Vt d |d{Vn8#tj$r&}t d|}Yd}~nd}~wwxYwdddd{Vn#1d{VswxYwYt d|r|dS)NrzSendToServer._send wait lockz SendToServer._send lock acquiredz&SendToServer._send cancelled unlockingz SendToServer._send lock released)r9r5waitmaxr1r[rrr3rurrr}r2rLr_PENDING_MESSAGES_LIMITrbr|r7r~r`)r)timeoutrLneed_to_canceles r-r=zSendToServerClient._send!s'   '.%%'' ' ' ' ' ' ' ' '!T1DJOO4E4EEFFG$W%9:: G G&t~':':'<'z6SendToServerClient._decode_message..Ls#EEEAf1r.)rloadsrZr updaterYr )r)rhrrs r-_decode_messagez"SendToServerClient._decode_messageHspz'"" 88F   d6l++C JJEEEEE F F FJt}}r.c|dddz|d<||}|7|j|||jdS|j||dS)Napi_retries_countr)rW)rZrmr2rryupdate_message)r)rXrWrhencodeds r-_persist_failedz"SendToServerClient._persist_failedPs'.{{3F'J'JQ'N#$33G<<   M  g  ; ; ; M 0 0 2 2 2 2 2 M ( (W = = = = =r.cKtj||}|dtj|j} tj||htjd{V\}}n<#tj$r*||wxYw|D]}t|d{V||vr| dSdS)zRace the HTTP send against the shutdown signal. Returns True on success, raises on API error, or returns False if shutdown interrupted the send. c6t|tjSr%)rrbdebug)tasks r-z6SendToServerClient._send_one_message..cs*4>>r.) return_whenNTF) r3 ensure_futureradd_done_callbackr8rFIRST_COMPLETEDr`cancelrresult)r)rorh send_task shutdown_taskdone pending_tasksrs r-_send_one_messagez$SendToServerClient._send_one_messageZsW )#*:*:7*C*CDD ## > >    -d.A.F.F.H.HII  (/ M*#3)))###### D--%           " " "  " ) )D"4(( ( ( ( ( ( ( ( (         4us ,+B9CcK|jrtddS||}|d|dd} t j|td| ||d{V}|std dS|drit j|td t d |tj |d||j|gd S#t"t$f$r>}td ||||||Yd}~dSd}~wt($r>}td ||||||Yd}~dSd}~wwxYw)zDeliver one message and return (stop, failed); message_id is None for a fresh memory-only message, set for a stored row.z4Shutdown signal received, keeping remaining messages)TFmethodrX)rrXz agent-sendingrNz@Shutdown signal received during send, keeping remaining messagesz agent-sentzmessage sent %s)FFz'Failed to send message %s to server: %s)TT)FT)r8is_setrbrcrrZrr_reporter_gen_sendingr_reporter_gen_sentr|rregistryconfirmr2deleterrrr) r)rorXrW message_bytesrhmsg_inforfris r- _try_send_onez SendToServerClient._try_send_onezsG   % % ' '  NNF   ;&&}55kk(++!++l33  !   .o    //W========D #2#{! I /| -x888%--gkk,.G.GHHH% $$j\222<'7    NN98S     Y @ @ @:::::    NN98S     Y @ @ @;;;;;  s,9AEBEG403F)) G463G//G4cK|5}|j ddddSt|jd|jDz}t dt|d}d} |D]R\}}}| ||||d{V\}} |r| sn&|dz }| r|dz }|r|t||z z }nSd||dD} | r3|j | |j nO#d||dD} | r4|j | |j wwxYwt d|ddddS#1swxYwYdS)Ncg|] \}}d||f Sr%rT)rUrWrs r-rVz=SendToServerClient._send_pending_messages..s3999,I}y-0999r.zSending %s messagesrrc"g|] \}}}|||f Sr%rT)rUmidtsmbs r-rVz=SendToServerClient._send_pending_messages..s/%%%#R{H"{{r.z Unsuccessful to send %s messages) rk server_idrr2 peek_stored drain_bufferrbr|lenrput_manyry) r)robatch failure_count processedrXrWrrsfailedunattempted_freshs r-r~z)SendToServerClient._send_pending_messagess ]]__% K}$% K% K% K% K% K% K% K% K2244559904 0J0J0L0L999E KK-s5zz : : :MI ;F>)rEN)6__name__ __module__ __qualname____doc__intrrrZrrr_SEND_MESSAGE_RECURRING_TIME_METRICS_REPORT_INTERVALrtrr'r3AbstractEventLooprArr;dictrJrOrCr boolr_rzrs staticmethodrrcontextmanagerrrrrkrr Reportablerr?rrr=bytesrmrrrrr~ __classcell__)r,s@r-rr2s77"c 92>>O,N#% %"""""  g&?     _-..##/.# $         ,+,+,+\'d!E!E!E!EF...`\ +)L$?t$KL++++ VK "## NG N N N N$# N_12232$$$$$_Q ! ! !DUu>>>@///b&K&K&K&K&K&K&K&Kr.rc0eZdZejZdZdedefdZ dS) SendToServerirhrEcK|5}|j ddddS||d{Vdddn #1swxYwYdS)NFT)rkrrrns r-r_z!SendToServer._send_metrics_directs ]]__ ,}$ , , , , , , , ,""7++ + + + + + + + , , , , , , , , , , , , , , ,ts AAAAN) rrrrAVSCOPESHUTDOWN_PRIORITYr rr_rTr.r-rrsC HE'dr.r)6r3concurrent.futuresrrrloggingrr[r\typingrdefence360agent.api.serverrrrrdefence360agent.contractsr defence360agent.contracts.configr "defence360agent.contracts.messagesr r r r !defence360agent.contracts.pluginsrrdefence360agent.internalsrr'defence360agent.internals.feature_flagsrr2defence360agent.internals.message_status_publisherrr,defence360agent.internals.persistent_messagerdefence360agent.utilsrrrrdefence360agent.utils.jsonr getLoggerrrbrrrrrrTr.r-rs   .-----111111 BAAAAAAAAAAAAAAANMMMMMMM 988888  8 $ $suuSUU_K_K_K_K_K_K_K_KD     %{     r.defence360agent/plugins/__pycache__/config_merger.cpython-311.opt-1.pyc0000644000000000000000000000424700000000000022715 0ustar r_j<vddlZddlmZmZddlmZddlmZmZej e Z GddeZ dS)N)ConfigValidationErrorMerger) MessageType) MessageSinkexpectcdeZdZejjZdZdZe e j dZ dS) ConfigMergercd|_dSNloop)selfs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_merger.py__init__zConfigMerger.__init__ s  cK||_dSr r )rr s r create_sinkzConfigMerger.create_sinks rcTK tjn2#t$r%}td|Yd}~nd}~wwxYw|dx}r|dSdS#|dx}r|wwxYw)Nz&Config is invalid. Will not update: %sevent)rupdate_merged_configrloggererrorgetset)rmessageerrrs rrz!ConfigMerger.update_merged_configs   ' ) ) ) )$ H H H LLA3 G G G G G G G G H G,,,u     G,,,u   s*A9 AAA9AA99.B'N) __name__ __module__ __qualname__rProcessingOrderPRE_PROCESS_MESSAGEPROCESSING_ORDERrrrr ConfigUpdaterrrr r sg"2F VK $%%&%rr ) logging defence360agent.contracts.configrr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrr getLoggerrrr r$rrr*sJJJJJJJJ::::::AAAAAAAA  8 $ $;rdefence360agent/plugins/__pycache__/config_merger.cpython-311.pyc0000644000000000000000000000424700000000000021756 0ustar r_j<vddlZddlmZmZddlmZddlmZmZej e Z GddeZ dS)N)ConfigValidationErrorMerger) MessageType) MessageSinkexpectcdeZdZejjZdZdZe e j dZ dS) ConfigMergercd|_dSNloop)selfs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_merger.py__init__zConfigMerger.__init__ s  cK||_dSr r )rr s r create_sinkzConfigMerger.create_sinks rcTK tjn2#t$r%}td|Yd}~nd}~wwxYw|dx}r|dSdS#|dx}r|wwxYw)Nz&Config is invalid. Will not update: %sevent)rupdate_merged_configrloggererrorgetset)rmessageerrrs rrz!ConfigMerger.update_merged_configs   ' ) ) ) )$ H H H LLA3 G G G G G G G G H G,,,u     G,,,u   s*A9 AAA9AA99.B'N) __name__ __module__ __qualname__rProcessingOrderPRE_PROCESS_MESSAGEPROCESSING_ORDERrrrr ConfigUpdaterrrr r sg"2F VK $%%&%rr ) logging defence360agent.contracts.configrr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrr getLoggerrrr r$rrr*sJJJJJJJJ::::::AAAAAAAA  8 $ $;rdefence360agent/plugins/__pycache__/config_watcher.cpython-311.opt-1.pyc0000644000000000000000000000653100000000000023067 0ustar r_jddlZddlmZddlmZddlmZmZmZddl m Z m Z ej ddZ Gdd eeZdS) N)config) MessageType) MessageSink MessageSourceexpect)recurring_checkScopeREAD_CONFIG_POLLING_INTERVALceZdZdZejZdZdZe e j dZ dZ dZeedZdS) ConfigWatcherzSend ConfigUpdate message on [root's] config update. The config update is detected by polling config file's modification time. c`tj|_d|_d|_d|_dS)Nr)r ConfigFile_config_last_notify_time_sink_task)selfs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_watcher.py__init__zConfigWatcher.__init__s,(** !"  c KdS)zplugins.MessageSink methodN)rloops r create_sinkzConfigWatcher.create_sinks rc$K|d|_dS)N timestamp)rrmessages ron_config_update_messagez&ConfigWatcher.on_config_update_message!s")!5rcpK||_|||_dSN)r create_task _check_configr)rrsinks r create_sourcezConfigWatcher.create_source's2 %%d&8&8&:&:;; rc|K|j+|jdc}|_||d{Vd|_dSr")rcancelr)rts rshutdownzConfigWatcher.shutdown+sF : ! JMAtz HHJJJGGGGGGG rcKtj|jr[tj|jt j}|j|d{V|d|_dSdS)N)confrr) rany_layer_modified_sincerr ConfigUpdatertimerprocess_messagers rr$zConfigWatcher._check_config2s  *4+A B B :!.\TY[[G*,,W55 5 5 5 5 5 5 5&-[%9D " " " : :rN)__name__ __module__ __qualname____doc__r AVSCOPErrrrr.r r&r*rPOLLING_INTERVALr$rrrr r s HE %%% VK $%%66&%6 <<<_%&& : :'& : : :rr )r/defence360agent.contractsr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrrdefence360agent.utilsrr int_from_envvarr7r rrrr=s ,,,,,,:::::: 98888888)6)*H"MM.:.:.:.:.:K.:.:.:.:.:rdefence360agent/plugins/__pycache__/config_watcher.cpython-311.pyc0000644000000000000000000000653100000000000022130 0ustar r_jddlZddlmZddlmZddlmZmZmZddl m Z m Z ej ddZ Gdd eeZdS) N)config) MessageType) MessageSink MessageSourceexpect)recurring_checkScopeREAD_CONFIG_POLLING_INTERVALceZdZdZejZdZdZe e j dZ dZ dZeedZdS) ConfigWatcherzSend ConfigUpdate message on [root's] config update. The config update is detected by polling config file's modification time. c`tj|_d|_d|_d|_dS)Nr)r ConfigFile_config_last_notify_time_sink_task)selfs [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/config_watcher.py__init__zConfigWatcher.__init__s,(** !"  c KdS)zplugins.MessageSink methodN)rloops r create_sinkzConfigWatcher.create_sinks rc$K|d|_dS)N timestamp)rrmessages ron_config_update_messagez&ConfigWatcher.on_config_update_message!s")!5rcpK||_|||_dSN)r create_task _check_configr)rrsinks r create_sourcezConfigWatcher.create_source's2 %%d&8&8&:&:;; rc|K|j+|jdc}|_||d{Vd|_dSr")rcancelr)rts rshutdownzConfigWatcher.shutdown+sF : ! JMAtz HHJJJGGGGGGG rcKtj|jr[tj|jt j}|j|d{V|d|_dSdS)N)confrr) rany_layer_modified_sincerr ConfigUpdatertimerprocess_messagers rr$zConfigWatcher._check_config2s  *4+A B B :!.\TY[[G*,,W55 5 5 5 5 5 5 5&-[%9D " " " : :rN)__name__ __module__ __qualname____doc__r AVSCOPErrrrr.r r&r*rPOLLING_INTERVALr$rrrr r s HE %%% VK $%%66&%6 <<<_%&& : :'& : : :rr )r/defence360agent.contractsr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrrdefence360agent.utilsrr int_from_envvarr7r rrrr=s ,,,,,,:::::: 98888888)6)*H"MM.:.:.:.:.:K.:.:.:.:.:rdefence360agent/plugins/__pycache__/event_hook_executor.cpython-311.opt-1.pyc0000644000000000000000000000343700000000000024166 0ustar r_j ddlmZddlmZmZmZddlmZejej ej ej ej fZ GddeeZdS)) HookEvent) MessageSink MessageSourceexpect) execute_hookscNeZdZejjZdZdZe e dZ dS)EventHookExecutorcK||_dSN)_loop)selfloops `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_hook_executor.py create_sinkzEventHookExecutor.create_sinks c&K||_||_dSr )r _sink)r rsinks r create_sourcezEventHookExecutor.create_sources  rcXK|jt|dSr )r create_taskr)r events r receive_eventzEventHookExecutor.receive_events* }U3344444rN) __name__ __module__ __qualname__rProcessingOrder EVENT_HOOKPROCESSING_ORDERrrrEVENTSrrrr r s_"2= VV_55_555rr N)%defence360agent.contracts.hook_eventsr!defence360agent.contracts.pluginsrrrdefence360agent.hooks.executer AgentStartedAgentMisconfigLicenseExpiredLicenseExpiringLicenseRenewedr r r!rrr*s;;;;;; 877777       5 5 5 5 5 ] 5 5 5 5 5rdefence360agent/plugins/__pycache__/event_hook_executor.cpython-311.pyc0000644000000000000000000000343700000000000023227 0ustar r_j ddlmZddlmZmZmZddlmZejej ej ej ej fZ GddeeZdS)) HookEvent) MessageSink MessageSourceexpect) execute_hookscNeZdZejjZdZdZe e dZ dS)EventHookExecutorcK||_dSN)_loop)selfloops `/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_hook_executor.py create_sinkzEventHookExecutor.create_sinks c&K||_||_dSr )r _sink)r rsinks r create_sourcezEventHookExecutor.create_sources  rcXK|jt|dSr )r create_taskr)r events r receive_eventzEventHookExecutor.receive_events* }U3344444rN) __name__ __module__ __qualname__rProcessingOrder EVENT_HOOKPROCESSING_ORDERrrrEVENTSrrrr r s_"2= VV_55_555rr N)%defence360agent.contracts.hook_eventsr!defence360agent.contracts.pluginsrrrdefence360agent.hooks.executer AgentStartedAgentMisconfigLicenseExpiredLicenseExpiringLicenseRenewedr r r!rrr*s;;;;;; 877777       5 5 5 5 5 ] 5 5 5 5 5rdefence360agent/plugins/__pycache__/event_monitor.cpython-311.opt-1.pyc0000644000000000000000000001412100000000000022767 0ustar r_j ddlZddlmZddlmZddlmZddlmZm Z m Z ddl m Z ddl mZddlmZdd lmZdd lmZmZdd lmZmZeeZGd d eeZdS)N)ABC) getLogger)Path)DictListOptional)Core) MessageType) MessageSource)%NativeFeatureManagementSettingsChange)EventProcessorBaseUserConfigProcessor)recurring_checksafe_cancel_taskceZdZejZdZdZdZdZ e de fdZ e de de fdZdeejfd Zed d Zd S) EventMonitorz *.*.*.*.jsonc>d|_d|_g|_d|_dSN)_loop_sink _processors_processing_taskselfs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor.py__init__zEventMonitor.__init__s&  57 $c$K||_||_|jt ||jt ||j||_dSr) rrrappendr r create_task#_check_inbox_folder_generate_eventsr)rloopsinks r create_sourcezEventMonitor.create_source s    Ed K KLLL  3D 9 9::: $ 6 6  4 4 6 6! ! rc>Kt|jd{VdSr)rrrs rshutdownzEventMonitor.shutdown)s/t455555555555rfilec |dS#t$rYdSt$r'}td||Yd}~dSd}~wwxYw)NzCouldn't remove file %s %s)unlinkFileNotFoundError Exceptionloggerwarning)r'es r_rmfilezEventMonitor._rmfile,s~ B KKMMMMM     DD B B B NN7q A A A A A A A A A Bs A AAAreturncNtj|Sr)jsonloads read_text)r's r _from_jsonzEventMonitor._from_json5sz$..**+++rc |jd^}}}}}t|dz|z}n,#t$rtd|YdSwxYw t j|||| |S#t$rtd|Yn/tj $rtd|YnwxYwdS)N.z+hook-event-file detected with wrong name %s)usernamehooktsfieldszhook file disappeared %szhook file have broken json %s) namesplitfloat ValueErrorr,r-r cPanelEventfrom_hook_eventr5r*r2JSONDecodeError)rr'r8r9ts1ts2_r:s r_event_to_messagezEventMonitor._event_to_message9s$ +/9??3+?+? (HdCqsSy3''BB    NNH$ O O O44  B*::!t,, ;  ! = = = NN5t < < < < <# B B B NN:D A A A A A Bts'47%A A $5B%C-)C-,C-c Kt|jdD]} ||}|9|jD]1}|d{Vr||2n2#t$r%}t d|Yd}~nd}~wwxYw| |#| |wxYw|jD]}| d{VdS)Nz *.*.*.jsonzFailed to process %s hook event) r EVENT_DIRglobrFr is_enabled add_messager+r,errorr/process_messages)rr'message processorexcs rr!z0EventMonitor._check_inbox_folder_generate_eventsOsR((--l;; # #D #0066&%)%5;; !*!5!5!7!7777777;%11'::: E E E >DDDDDDDD E T"""" T"""") / /I,,.. . . . . . . . . / /s0AA>=C> B-B(#C(B--CCN)__name__ __module__ __qualname__r INBOX_HOOKS_DIRrIPATTERNrr$r& staticmethodrr/rr5rr r@rFrr!rrrrs$IG%%%    666BdBBB\B,,$,,,\,+2I)J,_R / / / / /rr)r2abcrloggingrpathlibrtypingrrr defence360agent.contracts.configr "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr 1defence360agent.feature_management.plugins.nativer 7defence360agent.plugins.event_monitor_message_processorr rdefence360agent.utilsrrrRr,rrXrrrcsH ''''''''''111111::::::;;;;;;DCCCCCCC 8  G/G/G/G/G/=#G/G/G/G/G/rdefence360agent/plugins/__pycache__/event_monitor.cpython-311.pyc0000644000000000000000000001412100000000000022030 0ustar r_j ddlZddlmZddlmZddlmZddlmZm Z m Z ddl m Z ddl mZddlmZdd lmZdd lmZmZdd lmZmZeeZGd d eeZdS)N)ABC) getLogger)Path)DictListOptional)Core) MessageType) MessageSource)%NativeFeatureManagementSettingsChange)EventProcessorBaseUserConfigProcessor)recurring_checksafe_cancel_taskceZdZejZdZdZdZdZ e de fdZ e de de fdZdeejfd Zed d Zd S) EventMonitorz *.*.*.*.jsonc>d|_d|_g|_d|_dSN)_loop_sink _processors_processing_taskselfs Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor.py__init__zEventMonitor.__init__s&  57 $c$K||_||_|jt ||jt ||j||_dSr) rrrappendr r create_task#_check_inbox_folder_generate_eventsr)rloopsinks r create_sourcezEventMonitor.create_source s    Ed K KLLL  3D 9 9::: $ 6 6  4 4 6 6! ! rc>Kt|jd{VdSr)rrrs rshutdownzEventMonitor.shutdown)s/t455555555555rfilec |dS#t$rYdSt$r'}td||Yd}~dSd}~wwxYw)NzCouldn't remove file %s %s)unlinkFileNotFoundError Exceptionloggerwarning)r'es r_rmfilezEventMonitor._rmfile,s~ B KKMMMMM     DD B B B NN7q A A A A A A A A A Bs A AAAreturncNtj|Sr)jsonloads read_text)r's r _from_jsonzEventMonitor._from_json5sz$..**+++rc |jd^}}}}}t|dz|z}n,#t$rtd|YdSwxYw t j|||| |S#t$rtd|Yn/tj $rtd|YnwxYwdS)N.z+hook-event-file detected with wrong name %s)usernamehooktsfieldszhook file disappeared %szhook file have broken json %s) namesplitfloat ValueErrorr,r-r cPanelEventfrom_hook_eventr5r*r2JSONDecodeError)rr'r8r9ts1ts2_r:s r_event_to_messagezEventMonitor._event_to_message9s$ +/9??3+?+? (HdCqsSy3''BB    NNH$ O O O44  B*::!t,, ;  ! = = = NN5t < < < < <# B B B NN:D A A A A A Bts'47%A A $5B%C-)C-,C-c Kt|jdD]} ||}|9|jD]1}|d{Vr||2n2#t$r%}t d|Yd}~nd}~wwxYw| |#| |wxYw|jD]}| d{VdS)Nz *.*.*.jsonzFailed to process %s hook event) r EVENT_DIRglobrFr is_enabled add_messager+r,errorr/process_messages)rr'message processorexcs rr!z0EventMonitor._check_inbox_folder_generate_eventsOsR((--l;; # #D #0066&%)%5;; !*!5!5!7!7777777;%11'::: E E E >DDDDDDDD E T"""" T"""") / /I,,.. . . . . . . . . / /s0AA>=C> B-B(#C(B--CCN)__name__ __module__ __qualname__r INBOX_HOOKS_DIRrIPATTERNrr$r& staticmethodrr/rr5rr r@rFrr!rrrrs$IG%%%    666BdBBB\B,,$,,,\,+2I)J,_R / / / / /rr)r2abcrloggingrpathlibrtypingrrr defence360agent.contracts.configr "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr 1defence360agent.feature_management.plugins.nativer 7defence360agent.plugins.event_monitor_message_processorr rdefence360agent.utilsrrrRr,rrXrrrcsH ''''''''''111111::::::;;;;;;DCCCCCCC 8  G/G/G/G/G/=#G/G/G/G/G/rdefence360agent/plugins/__pycache__/event_monitor_message_processor.cpython-311.opt-1.pyc0000644000000000000000000003220600000000000026576 0ustar r_joddlZddlZddlZddlmZmZddlmZddlm Z m Z ddl m Z ddl mZddlmZddlmZmZdd lmZmZejZGd d eeZGd d eeZGddeZdS)N)ABCabstractmethod) defaultdict)heappopheappush)Dict)Core) MessageType)BaseMessageProcessorexpect)is_safe_subdir_namermtreeceZdZdZdZdZeejdZ dZ e dZ e dZ e dZe d Ze d Ze d Zd S) EventProcessorBasecFtt|_||_dSN)rlist_msg_buf_loop)selfloops l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor_message_processor.py__init__zEventProcessorBase.__init__s#D))  cXt|j|d|d|fdS)Nusername timestamp)rrrmessages r add_messagezEventProcessorBase.add_messages9 M'*- .1Ew0O     rczKtjfdjDd{VdS)Nc3BK|]}|VdSr)process_user_messages).0 user_messagesrs r z6EventProcessorBase.process_messages..sE!**=99r)asynciogatherrvaluesrs`rprocess_messagesz#EventProcessorBase.process_messagessrn%)]%9%9%;%;          rcxK||sdS|jdkr||d{VdS|jdkr||d{VdS|jdkr||d{VdS|jdkr||d{VdSdS)NModifyCreatechange_packageRemove)_message_is_relatablehook_process_modify_process_create_process_change_package_process_account_removedrs r process_eventz EventProcessorBase.process_event$s))'22  F <8 # #&&w// / / / / / / / / / \X % %&&w// / / / / / / / / / \- - -..w77 7 7 7 7 7 7 7 7 7 \X % %//88 8 8 8 8 8 8 8 8 8& %rcKtt|D]0}|t|dd{V1dS)N)rangelenprocess_messager)rmessages_s rr#z(EventProcessorBase.process_user_messages2scs8}}%% = =A&&wx'8'8';<< < < < < < < < < = =rc KdS)z Modify hookNrs rr3z"EventProcessorBase._process_modify6 rc KdSz Create hookNr@rs rr4z"EventProcessorBase._process_create:rArc KdSzchange_package hookNr@rs rr5z*EventProcessorBase._process_change_package>rArc KdS)z Remove hookNr@rs rr6z+EventProcessorBase._process_account_removedBrArcdSz'Whether the message should be processedNr@rs rr1z(EventProcessorBase._message_is_relatableFrc KdSz$Whether messages should be processedNr@r*s r is_enabledzEventProcessorBase.is_enabledJrArN)__name__ __module__ __qualname__rr r+r r cPanelEventr7r#rr3r4r5r6r1rLr@rrrrs        VK #$$ 9 9%$ 9===^^""^"^66^633^333rrc eZdZdZdZdZdZdZ ddede d d fd Z d Z e d Z e dZee d eeeffdZe dZee dede d eeeffdZe dZd S)SettingsChangeBasez'Process hook event messages from cPanelc\Kd|jvrdnd}|||d{VdS)Nplanexclude)data_get_settings_and_update)rr package_fields rr3z"SettingsChangeBase._process_modifyRsI"(GL"8"8i ++G]CCCCCCCCCCCrcDK||ddd{VdS)NrTTrWrs rr4z"SettingsChangeBase._process_createVs6++GVTBBBBBBBBBBBrcDK||ddd{VdS)Nnew_pkgTrZrs rr5z*SettingsChangeBase._process_change_packageYs6++GYEEEEEEEEEEErc KdSrr@rs rr6z+SettingsChangeBase._process_account_removed\s  rFrXadd_to_packagereturnNcKtd|||d{V}|||||d{VdS)NzGet settings from %s)loggerinfo_get_settings_from_message_apply_settings)rrrXr^settingss rrWz+SettingsChangeBase._get_settings_and_update_s  *G44488AAAAAAAA"" ]NH           rcKtd||d9|ddkr-td|DrdSt|s |j|}|||d{V}n>#t$r1td| }YnwxYw| D]\}}| ||||<td||d| D](\}}| |d||d{V)dS) Nz Step 1 %s rTr2r-c3K|]}|duV dSrr@)r$values rr&z5SettingsChangeBase._apply_settings..ts&AAeETMAAAAAArz'No information about package in messagez,Settings specified in hook message %s for %sr) rarbgetallr)rV_get_package_settingsKeyErrorwarning_default_settingsitemson_settings_change) rrrXr^re package_namefallback_settingsfeaturerhs rrdz"SettingsChangeBase._apply_settingsks  L(+++ KK   '8++AAx/@/@AAAAA, F8??$$%% C &|M: +/*D*D .++%%%%%%!!  = = =HIII$($:$:$<$<!!! =#+.."2"2 C C=(9'(BHW% :  J    'nn.. O ONGU))'**=wNN N N N N N N N N O Os B888C32C3cdSrHr@rs rr1z(SettingsChangeBase._message_is_relatablerIrc KdS)z9What to do after settings were changed (e.g. sync the DB)Nr@)ruserrsrhs rrpz%SettingsChangeBase.on_settings_changerArcdS)zGet default package settingsNr@r@rrrnz$SettingsChangeBase._default_settingsrIrc KdS)z"Retrieve settings from the messageNr@rs rrcz-SettingsChangeBase._get_settings_from_messagerArrqc KdS)zGet current package settingsNr@)clsrqr^s rrkz(SettingsChangeBase._get_package_settingsrArc KdSrKr@r*s rrLzSettingsChangeBase.is_enabledrAr)F)rMrNrO__doc__r3r4r5r6strboolrWrdrr1rp staticmethodrrnrc classmethodrkrLr@rrrRrROs11DDDCCCFFF    %            OOOB66^6HH^H+tCH~+++^\+11^1++04+ c3h+++^[+ 33^333rrRc2eZdZdZdZdZdZdZdZdS)UserConfigProcessorcdSNTr@rs rr1z)UserConfigProcessor._message_is_relatablestrc KdSrr@r*s rrLzUserConfigProcessor.is_enableds trctK|dp|d}t|sdStjt j|} t|dS#t$rYdSt$r'}t d||Yd}~dSd}~wwxYw)Nrvrz'Failed to remove user_config dir %s: %s) rir ospathjoinr USER_CONFDIRrFileNotFoundErrorOSErrorrarm)rrrvtargetes rr6z,UserConfigProcessor._process_account_removeds{{6""=gkk*&=&="4((  Fd/66  6NNNNN     DD    NN961          s)A:: B7 B7B22B7cK|jd}|j}|rt|rt|sdS t jtjtj |tjtj |dS#t$rYdSt$r(}t d|||Yd}~dSd}~wwxYw)N old_usernamez)Failed to rename user_config %s -> %s: %s)rVrirr rrenamerrr rrrrarm)rrr new_usernamers rr3z#UserConfigProcessor._process_modifys|''77 '  #L11 $L11  F  I T. == T. ==     !    DD    NN;           sA%B.. C,; C,C''C,c KdSrCr@rs rr4z#UserConfigProcessor._process_createrArc KdSrEr@rs rr5z+UserConfigProcessor._process_change_packagerArN) rMrNrOr1rLr6r3r4r5r@rrrrsn   0"""""rr)r'loggingrabcrr collectionsrheapqrrtypingr defence360agent.contracts.configr "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr r defence360agent.utilsr r getLoggerrarrRrr@rrrsq ######################111111::::::JJJJJJJJ========    ;3;3;3;3;3-s;3;3;3|W3W3W3W3W3+SW3W3W3t1"1"1"1"1",1"1"1"1"1"rdefence360agent/plugins/__pycache__/event_monitor_message_processor.cpython-311.pyc0000644000000000000000000003220600000000000025637 0ustar r_joddlZddlZddlZddlmZmZddlmZddlm Z m Z ddl m Z ddl mZddlmZddlmZmZdd lmZmZejZGd d eeZGd d eeZGddeZdS)N)ABCabstractmethod) defaultdict)heappopheappush)Dict)Core) MessageType)BaseMessageProcessorexpect)is_safe_subdir_namermtreeceZdZdZdZdZeejdZ dZ e dZ e dZ e dZe d Ze d Ze d Zd S) EventProcessorBasecFtt|_||_dSN)rlist_msg_buf_loop)selfloops l/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/event_monitor_message_processor.py__init__zEventProcessorBase.__init__s#D))  cXt|j|d|d|fdS)Nusername timestamp)rrrmessages r add_messagezEventProcessorBase.add_messages9 M'*- .1Ew0O     rczKtjfdjDd{VdS)Nc3BK|]}|VdSr)process_user_messages).0 user_messagesrs r z6EventProcessorBase.process_messages..sE!**=99r)asynciogatherrvaluesrs`rprocess_messagesz#EventProcessorBase.process_messagessrn%)]%9%9%;%;          rcxK||sdS|jdkr||d{VdS|jdkr||d{VdS|jdkr||d{VdS|jdkr||d{VdSdS)NModifyCreatechange_packageRemove)_message_is_relatablehook_process_modify_process_create_process_change_package_process_account_removedrs r process_eventz EventProcessorBase.process_event$s))'22  F <8 # #&&w// / / / / / / / / / \X % %&&w// / / / / / / / / / \- - -..w77 7 7 7 7 7 7 7 7 7 \X % %//88 8 8 8 8 8 8 8 8 8& %rcKtt|D]0}|t|dd{V1dS)N)rangelenprocess_messager)rmessages_s rr#z(EventProcessorBase.process_user_messages2scs8}}%% = =A&&wx'8'8';<< < < < < < < < < = =rc KdS)z Modify hookNrs rr3z"EventProcessorBase._process_modify6 rc KdSz Create hookNr@rs rr4z"EventProcessorBase._process_create:rArc KdSzchange_package hookNr@rs rr5z*EventProcessorBase._process_change_package>rArc KdS)z Remove hookNr@rs rr6z+EventProcessorBase._process_account_removedBrArcdSz'Whether the message should be processedNr@rs rr1z(EventProcessorBase._message_is_relatableFrc KdSz$Whether messages should be processedNr@r*s r is_enabledzEventProcessorBase.is_enabledJrArN)__name__ __module__ __qualname__rr r+r r cPanelEventr7r#rr3r4r5r6r1rLr@rrrrs        VK #$$ 9 9%$ 9===^^""^"^66^633^333rrc eZdZdZdZdZdZdZ ddede d d fd Z d Z e d Z e dZee d eeeffdZe dZee dede d eeeffdZe dZd S)SettingsChangeBasez'Process hook event messages from cPanelc\Kd|jvrdnd}|||d{VdS)Nplanexclude)data_get_settings_and_update)rr package_fields rr3z"SettingsChangeBase._process_modifyRsI"(GL"8"8i ++G]CCCCCCCCCCCrcDK||ddd{VdS)NrTTrWrs rr4z"SettingsChangeBase._process_createVs6++GVTBBBBBBBBBBBrcDK||ddd{VdS)Nnew_pkgTrZrs rr5z*SettingsChangeBase._process_change_packageYs6++GYEEEEEEEEEEErc KdSrr@rs rr6z+SettingsChangeBase._process_account_removed\s  rFrXadd_to_packagereturnNcKtd|||d{V}|||||d{VdS)NzGet settings from %s)loggerinfo_get_settings_from_message_apply_settings)rrrXr^settingss rrWz+SettingsChangeBase._get_settings_and_update_s  *G44488AAAAAAAA"" ]NH           rcKtd||d9|ddkr-td|DrdSt|s |j|}|||d{V}n>#t$r1td| }YnwxYw| D]\}}| ||||<td||d| D](\}}| |d||d{V)dS) Nz Step 1 %s rTr2r-c3K|]}|duV dSrr@)r$values rr&z5SettingsChangeBase._apply_settings..ts&AAeETMAAAAAArz'No information about package in messagez,Settings specified in hook message %s for %sr) rarbgetallr)rV_get_package_settingsKeyErrorwarning_default_settingsitemson_settings_change) rrrXr^re package_namefallback_settingsfeaturerhs rrdz"SettingsChangeBase._apply_settingsks  L(+++ KK   '8++AAx/@/@AAAAA, F8??$$%% C &|M: +/*D*D .++%%%%%%!!  = = =HIII$($:$:$<$<!!! =#+.."2"2 C C=(9'(BHW% :  J    'nn.. O ONGU))'**=wNN N N N N N N N N O Os B888C32C3cdSrHr@rs rr1z(SettingsChangeBase._message_is_relatablerIrc KdS)z9What to do after settings were changed (e.g. sync the DB)Nr@)ruserrsrhs rrpz%SettingsChangeBase.on_settings_changerArcdS)zGet default package settingsNr@r@rrrnz$SettingsChangeBase._default_settingsrIrc KdS)z"Retrieve settings from the messageNr@rs rrcz-SettingsChangeBase._get_settings_from_messagerArrqc KdS)zGet current package settingsNr@)clsrqr^s rrkz(SettingsChangeBase._get_package_settingsrArc KdSrKr@r*s rrLzSettingsChangeBase.is_enabledrAr)F)rMrNrO__doc__r3r4r5r6strboolrWrdrr1rp staticmethodrrnrc classmethodrkrLr@rrrRrROs11DDDCCCFFF    %            OOOB66^6HH^H+tCH~+++^\+11^1++04+ c3h+++^[+ 33^333rrRc2eZdZdZdZdZdZdZdZdS)UserConfigProcessorcdSNTr@rs rr1z)UserConfigProcessor._message_is_relatablestrc KdSrr@r*s rrLzUserConfigProcessor.is_enableds trctK|dp|d}t|sdStjt j|} t|dS#t$rYdSt$r'}t d||Yd}~dSd}~wwxYw)Nrvrz'Failed to remove user_config dir %s: %s) rir ospathjoinr USER_CONFDIRrFileNotFoundErrorOSErrorrarm)rrrvtargetes rr6z,UserConfigProcessor._process_account_removeds{{6""=gkk*&=&="4((  Fd/66  6NNNNN     DD    NN961          s)A:: B7 B7B22B7cK|jd}|j}|rt|rt|sdS t jtjtj |tjtj |dS#t$rYdSt$r(}t d|||Yd}~dSd}~wwxYw)N old_usernamez)Failed to rename user_config %s -> %s: %s)rVrirr rrenamerrr rrrrarm)rrr new_usernamers rr3z#UserConfigProcessor._process_modifys|''77 '  #L11 $L11  F  I T. == T. ==     !    DD    NN;           sA%B.. C,; C,C''C,c KdSrCr@rs rr4z#UserConfigProcessor._process_createrArc KdSrEr@rs rr5z+UserConfigProcessor._process_change_packagerArN) rMrNrOr1rLr6r3r4r5r@rrrrsn   0"""""rr)r'loggingrabcrr collectionsrheapqrrtypingr defence360agent.contracts.configr "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr r defence360agent.utilsr r getLoggerrarrRrr@rrrsq ######################111111::::::JJJJJJJJ========    ;3;3;3;3;3-s;3;3;3|W3W3W3W3W3+SW3W3W3t1"1"1"1"1",1"1"1"1"1"rdefence360agent/plugins/__pycache__/feature_flags.cpython-311.opt-1.pyc0000644000000000000000000003253500000000000022717 0ustar r_j"dZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z mZmZmZmZmZmZddlmZmZddlmZmZejeZdZd ed ed efd Z e!hd Z"e!hdZ#d ed e$d e$fdZ%e ddZ&e ddZ'e ddZ(e%ddZ)dZ*ded efdZ+Gdde Z,dS)u3 Feature flags synchronisation plugin (AV mode only). In IM360 mode the Go resident-agent handles feature-flag sync. In AV mode there is no resident-agent, so this plugin takes over. Periodically POSTs the local file checksum to the API and writes back any updated flags to ``/var/imunify360/feature_flags.json`` (legacy map ``{flag: true}`` on disk) and ``/var/imunify360/feature_flags`` (plain names, one per line). The POSTed checksum is over the canonical JSON **array** of enabled names, matching the correlation sync API—not over the on-disk map bytes. N)Core) MessageSource) FLAGS_PATHFLAGS_PLAIN_PATHenabled_flag_names_sorted$plain_text_payload_for_enabled_flags$serialize_feature_flags_file_payload!sync_checksum_hex_from_flags_filesync_response_file_bytes)IAIDTokenErrorIndependentAgentIDAPI)Scopeatomic_rewritez/api/sync/v1/feature-flagsnamedefaultreturnctj|}|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 plugin lives in the AV agent entry point and a bad env var would otherwise kill the whole agent. z4feature-flags: %s=%r is not an int, using default %d)osenvirongetint ValueErrorloggerwarning)rrraws Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/feature_flags.py_env_intr+s| *..  C  3xx  B       s4)A A >1onyestrue>0noofffalsectj|}|s|S|}|t vrdS|t vrdStd||||S)NTFz4feature-flags: %s=%r is not a bool, using default %s) rrrstriplower _TRUE_VALUES _FALSE_VALUESrr)rrr normalizeds r _env_boolr,Es *..  C ""$$J\!!t]""u NN>    N I360_FEATURE_FLAGS_SYNC_INTERVALiI360_FEATURE_FLAGS_INIT_DELAY I360_FEATURE_FLAGS_UNREG_DELAY#I360_FEATURE_FLAGS_USE_SERVER_DELAYT server_delayc.tr|dkr|StS)Nr)_USE_SERVER_DELAY_SYNC_INTERVAL)r4s r _next_delayr8^s \A-- r-ceZdZejZdZdZdefdZ dZ de fdZ e dejjdefdZe d d d Zd S) FeatureFlagsSyncc~K||_||_|||_dSN)_loop_sink create_task _sync_loop_task)selfloopsinks r create_sourcezFeatureFlagsSync.create_sourcegs7  %%doo&7&788 r-cK|j?|j |jd{VdS#tj$rYdSwxYwdSr<)rAcancelasyncioCancelledErrorrBs rshutdownzFeatureFlagsSync.shutdownlst : ! J      j         )     " !s 3AArc*ttSr<)r rrJs r_local_checksumz FeatureFlagsSync._local_checksumts0<<>E)    K K K:TJJJJJ K-&& & & & & & & & 'sAA..5B&%B&c K tjd{V}n+#t$rtdYdSwxYwt j}|d|jd{V}tj d|i }tj dtj}|dt"z}t$j||d|dd } |d|j|d{V}n#t$jj$ro} d | jcxkrd kr+nn(td | j|| jn'td | j|| jYd} ~ dSd} ~ wt$jjt6f$r6} td |t9| d| Yd} ~ dSd} ~ wt:$r!td|dYdSwxYw tj|} n0#tj$rtdYdSwxYw| dd} | ddurt!d| S| d} | dpi} | #|d|j"| | d{V| S)Nz*no IAID token, skipping feature flags syncrchecksumI360_FEATURE_FLAGS_API_URL/zapplication/json)z Content-TypezX-AuthPOST)dataheadersmethodiiXz$feature flags sync HTTP %s on %s: %sz.feature flags sync connection failed on %s: %sreasonz'feature flags sync request failed on %sTrOz&failed to parse feature flags responserWchangedFz'feature flags unchanged, skipping writeflagsparams)#r get_tokenr rrrHget_event_looprun_in_executorrMjsondumpsencodergetenvr API_BASE_URLrstrip _SYNC_URLurllibrequestRequest_blocking_requesterror HTTPErrorcoder`URLError TimeoutErrorgetattrrVloadsJSONDecodeErrorrdebug _write_flags)rBtokenrCrYpayloadbase_urlurlreq resp_bodyeresultr4rbrcs rrUzFeatureFlagsSync._do_syncs /9;;;;;;;;EE    NNG H H H11 %''--dD4HIIIIIIII*j(344;;==994;LMMooc""Y.n$$  2%  ( "22d,cII|%   af""""s""""" :FH :FH  11111 %|4    NN@8Q''    11111    LL9     11   Z **FF#    LLA B B B11 zz'1-- ::i E ) ) LLB C C C  7##H%%+  &&tT->vNN N N N N N N NsL$AA"D++H&?A$F))H&+G88*H&%H&*H??)I,+I,rctj|t5}|cdddS#1swxYwYdS)N)timeout)rnrourlopen _HTTP_TIMEOUTread)rresps rrqz"FeatureFlagsSync._blocking_requests ^ # #C # ? ? 499;;                  sAA A Nc|pi} t|tr;d|D}d|D}t||}nt |}n>#t $r1t dt|j YdSwxYwtt|} tj tjt dt#t |dt%|}t#t&|dt d |dS#t*$r t d d YdSwxYw) u^Persist flags + params on disk in the canonical sync-response shape so the next sync's checksum matches what the server returned. Falls back to the legacy ``{name: true}`` map when ``flags`` is not a list (response shape we don't recognise) — keeps the long-standing on-disk contract from older code paths. c<g|]}t|t|S isinstancestr).0ns r z1FeatureFlagsSync._write_flags..s'@@@qZ3-?-?@@@@r-ci|]<\}}t|tt|t/|d|D=S)c<g|]}t|t|Srr)rvs rrz..s'AAAjC.@.@A1AAAr-)rrlist)rrvalss r z1FeatureFlagsSync._write_flags..sa"d!$,,2rs_    111111;;;;;;87777777  8 $ $ ( 3,y33344  55566 C$4$>h?DDICTJJ cc `F`F`F`F`F}`F`F`F`F`Fr-defence360agent/plugins/__pycache__/feature_flags.cpython-311.pyc0000644000000000000000000003253500000000000021760 0ustar r_j"dZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z mZmZmZmZmZmZddlmZmZddlmZmZejeZdZd ed ed efd Z e!hd Z"e!hdZ#d ed e$d e$fdZ%e ddZ&e ddZ'e ddZ(e%ddZ)dZ*ded efdZ+Gdde Z,dS)u3 Feature flags synchronisation plugin (AV mode only). In IM360 mode the Go resident-agent handles feature-flag sync. In AV mode there is no resident-agent, so this plugin takes over. Periodically POSTs the local file checksum to the API and writes back any updated flags to ``/var/imunify360/feature_flags.json`` (legacy map ``{flag: true}`` on disk) and ``/var/imunify360/feature_flags`` (plain names, one per line). The POSTed checksum is over the canonical JSON **array** of enabled names, matching the correlation sync API—not over the on-disk map bytes. N)Core) MessageSource) FLAGS_PATHFLAGS_PLAIN_PATHenabled_flag_names_sorted$plain_text_payload_for_enabled_flags$serialize_feature_flags_file_payload!sync_checksum_hex_from_flags_filesync_response_file_bytes)IAIDTokenErrorIndependentAgentIDAPI)Scopeatomic_rewritez/api/sync/v1/feature-flagsnamedefaultreturnctj|}|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 plugin lives in the AV agent entry point and a bad env var would otherwise kill the whole agent. z4feature-flags: %s=%r is not an int, using default %d)osenvirongetint ValueErrorloggerwarning)rrraws Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/feature_flags.py_env_intr+s| *..  C  3xx  B       s4)A A >1onyestrue>0noofffalsectj|}|s|S|}|t vrdS|t vrdStd||||S)NTFz4feature-flags: %s=%r is not a bool, using default %s) rrrstriplower _TRUE_VALUES _FALSE_VALUESrr)rrr normalizeds r _env_boolr,Es *..  C ""$$J\!!t]""u NN>    N I360_FEATURE_FLAGS_SYNC_INTERVALiI360_FEATURE_FLAGS_INIT_DELAY I360_FEATURE_FLAGS_UNREG_DELAY#I360_FEATURE_FLAGS_USE_SERVER_DELAYT server_delayc.tr|dkr|StS)Nr)_USE_SERVER_DELAY_SYNC_INTERVAL)r4s r _next_delayr8^s \A-- r-ceZdZejZdZdZdefdZ dZ de fdZ e dejjdefdZe d d d Zd S) FeatureFlagsSyncc~K||_||_|||_dSN)_loop_sink create_task _sync_loop_task)selfloopsinks r create_sourcezFeatureFlagsSync.create_sourcegs7  %%doo&7&788 r-cK|j?|j |jd{VdS#tj$rYdSwxYwdSr<)rAcancelasyncioCancelledErrorrBs rshutdownzFeatureFlagsSync.shutdownlst : ! J      j         )     " !s 3AArc*ttSr<)r rrJs r_local_checksumz FeatureFlagsSync._local_checksumts0<<>E)    K K K:TJJJJJ K-&& & & & & & & & 'sAA..5B&%B&c K tjd{V}n+#t$rtdYdSwxYwt j}|d|jd{V}tj d|i }tj dtj}|dt"z}t$j||d|dd } |d|j|d{V}n#t$jj$ro} d | jcxkrd kr+nn(td | j|| jn'td | j|| jYd} ~ dSd} ~ wt$jjt6f$r6} td |t9| d| Yd} ~ dSd} ~ wt:$r!td|dYdSwxYw tj|} n0#tj$rtdYdSwxYw| dd} | ddurt!d| S| d} | dpi} | #|d|j"| | d{V| S)Nz*no IAID token, skipping feature flags syncrchecksumI360_FEATURE_FLAGS_API_URL/zapplication/json)z Content-TypezX-AuthPOST)dataheadersmethodiiXz$feature flags sync HTTP %s on %s: %sz.feature flags sync connection failed on %s: %sreasonz'feature flags sync request failed on %sTrOz&failed to parse feature flags responserWchangedFz'feature flags unchanged, skipping writeflagsparams)#r get_tokenr rrrHget_event_looprun_in_executorrMjsondumpsencodergetenvr API_BASE_URLrstrip _SYNC_URLurllibrequestRequest_blocking_requesterror HTTPErrorcoder`URLError TimeoutErrorgetattrrVloadsJSONDecodeErrorrdebug _write_flags)rBtokenrCrYpayloadbase_urlurlreq resp_bodyeresultr4rbrcs rrUzFeatureFlagsSync._do_syncs /9;;;;;;;;EE    NNG H H H11 %''--dD4HIIIIIIII*j(344;;==994;LMMooc""Y.n$$  2%  ( "22d,cII|%   af""""s""""" :FH :FH  11111 %|4    NN@8Q''    11111    LL9     11   Z **FF#    LLA B B B11 zz'1-- ::i E ) ) LLB C C C  7##H%%+  &&tT->vNN N N N N N N NsL$AA"D++H&?A$F))H&+G88*H&%H&*H??)I,+I,rctj|t5}|cdddS#1swxYwYdS)N)timeout)rnrourlopen _HTTP_TIMEOUTread)rresps rrqz"FeatureFlagsSync._blocking_requests ^ # #C # ? ? 499;;                  sAA A Nc|pi} t|tr;d|D}d|D}t||}nt |}n>#t $r1t dt|j YdSwxYwtt|} tj tjt dt#t |dt%|}t#t&|dt d |dS#t*$r t d d YdSwxYw) u^Persist flags + params on disk in the canonical sync-response shape so the next sync's checksum matches what the server returned. Falls back to the legacy ``{name: true}`` map when ``flags`` is not a list (response shape we don't recognise) — keeps the long-standing on-disk contract from older code paths. c<g|]}t|t|S isinstancestr).0ns r z1FeatureFlagsSync._write_flags..s'@@@qZ3-?-?@@@@r-ci|]<\}}t|tt|t/|d|D=S)c<g|]}t|t|Srr)rvs rrz..s'AAAjC.@.@A1AAAr-)rrlist)rrvalss r z1FeatureFlagsSync._write_flags..sa"d!$,,2rs_    111111;;;;;;87777777  8 $ $ ( 3,y33344  55566 C$4$>h?DDICTJJ cc `F`F`F`F`F}`F`F`F`F`Fr-defence360agent/plugins/__pycache__/files_recurring_update.cpython-311.opt-1.pyc0000644000000000000000000000532500000000000024631 0ustar r_ja~ddlZddlmZddlmZmZddlmZddlm Z ej e Z GddeZ dS)N)files)configmessages) MessageSource)recurring_checkcteZdZdejdeddfdZdZdZe e j j dZ dS) FilesRecurringUpdateTaskindex is_updatedreturnNcK|rGtj|j|}|j|d{VdSdSN)r MessageType FilesUpdatedtype_sinkprocess_message)selfr r messages c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/files_recurring_update.py_on_files_updatez)FilesRecurringUpdateTask._on_files_update sb  6*77 EJJG*,,W55 5 5 5 5 5 5 5 5 5 6 6c K||_||_|||_t jD]'}t j||j (dSr) _loopr create_task _update_task_taskrIndextypesadd_hookr)rloopsinktype_s r create_sourcez&FilesRecurringUpdateTask.create_sourcesz  %%d&7&7&9&9:: [&&(( ? ?E K (= > > > > ? ?rcVK|j|jd{VdSr)rcancelrs rshutdownz!FilesRecurringUpdateTask.shutdowns: jrc<Ktjd{VdSr)rupdate_and_log_errorr's rrz%FilesRecurringUpdateTask._update_task s-(***********r)__name__ __module__ __qualname__rrboolrr$r(rr FilesUpdatePERIODrrrr r s6[6.26 6666??? _V'.//++0/+++rr )loggingdefence360agentrdefence360agent.contractsrr!defence360agent.contracts.pluginsrdefence360agent.utilsr getLoggerr+loggerr r1rrr9s!!!!!!66666666;;;;;;111111  8 $ $+++++}+++++rdefence360agent/plugins/__pycache__/files_recurring_update.cpython-311.pyc0000644000000000000000000000532500000000000023672 0ustar r_ja~ddlZddlmZddlmZmZddlmZddlm Z ej e Z GddeZ dS)N)files)configmessages) MessageSource)recurring_checkcteZdZdejdeddfdZdZdZe e j j dZ dS) FilesRecurringUpdateTaskindex is_updatedreturnNcK|rGtj|j|}|j|d{VdSdSN)r MessageType FilesUpdatedtype_sinkprocess_message)selfr r messages c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/files_recurring_update.py_on_files_updatez)FilesRecurringUpdateTask._on_files_update sb  6*77 EJJG*,,W55 5 5 5 5 5 5 5 5 5 6 6c K||_||_|||_t jD]'}t j||j (dSr) _loopr create_task _update_task_taskrIndextypesadd_hookr)rloopsinktype_s r create_sourcez&FilesRecurringUpdateTask.create_sourcesz  %%d&7&7&9&9:: [&&(( ? ?E K (= > > > > ? ?rcVK|j|jd{VdSr)rcancelrs rshutdownz!FilesRecurringUpdateTask.shutdowns: jrc<Ktjd{VdSr)rupdate_and_log_errorr's rrz%FilesRecurringUpdateTask._update_task s-(***********r)__name__ __module__ __qualname__rrboolrr$r(rr FilesUpdatePERIODrrrr r s6[6.26 6666??? _V'.//++0/+++rr )loggingdefence360agentrdefence360agent.contractsrr!defence360agent.contracts.pluginsrdefence360agent.utilsr getLoggerr+loggerr r1rrr9s!!!!!!66666666;;;;;;111111  8 $ $+++++}+++++rdefence360agent/plugins/__pycache__/icontact_sender.cpython-311.opt-1.pyc0000644000000000000000000001577500000000000023263 0ustar r_j"ddlZddlZddlZddlmZddlmZddlmZddl m Z ddl m Z m Z ddlmZddlmZmZdd lmZdd lmZdd lmZdd lmZdd lmZddlmZmZm Z m!Z!m"Z"ddl#m$Z$ej%e&Z'dZ(GddeeZ)dS)N)Path)IAIDTokenError)APIError) EventsAPI)CoreIContactMessageType) MessageType) MessageSink MessageSource)TheSink)IContactThrottle)cPanel)Plesk) HostingPanel) await_forcreate_task_and_log_exceptionsrecurring_checkretry_onScope)DAYcbKtd||tddS)Nz[Can't get recommendations for the dashboard due to iaid token error, reason: %s. Attempt %sdseconds)loggerwarningr)eis \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/icontact_sender.pyasync_log_on_errorr #s@ NN 3   cceZdZejjZejZ fdZ dZ dddZ de fdZdZeeed d d e eeed d e defdZeedZxZS)IContactSenderctj|i|g|_ttjdz |_dS)Nicontact_generic_notifications)super__init___tasksrrTMPDIR_notification_flag_path)selfargskwargs __class__s rr'zIContactSender.__init__1sG$)&)))    @ @ $$$r!c KdSN)r+loops r create_sinkzIContactSender.create_sink8s  r!Nuserc|K|dStj|||sdS|jtj||d{V}|rmtj||tj|ttj |}|j |d{VdSdS)Nr4) message_typeparamsr5)r7 timestamp template_args) r may_be_notified_panelnotifyrGENERICrefreshr IContactSentinttime_sinkprocess_message)r+r7r8 period_limitr5r: sent_messages r_send_icontact_messagez%IContactSender._send_icontact_message;s    F/       F"k00,41         ;  $\ = = = =&3)dikk**+L *,,\:: : : : : : : : : : ; ;r!sinkcK||_t|_|jjtjt jfvrt ||jg|_dSdSr0) rCrr<NAMErrrgeneric_notificationsr()r+r2rHs r create_sourcezIContactSender.create_sourceYs[ "nn ;  UZ8 8 8.$4DKKK 9 8r!c~K|jD]}|tj|jddid{VdS)Nreturn_exceptionsT)r(cancelasynciogather)r+tasks rshutdownzIContactSender.shutdowncsUK  D KKMMMMndkBTBBBBBBBBBBBr! rT)on_error max_triessilentlogreturnc Kg}|jr;|jjtzt jkr5t jd{V}|jdd|S)NiT)modeexist_ok) r*existsstatst_mtimerrBr notificationtouch)r+ notificationss rget_notificationsz IContactSender.get_notificationshs ,3355 J,1133>2    r!)__name__ __module__ __qualname__r ProcessingOrder ICONTACT_SENTPROCESSING_ORDERrAV_IM360SCOPEr'r3rGr rLrSrrrrrr listrdrrrK __classcell__)r.s@rr#r#-sQ"2@ NE         ;;;;;<gCCC X2&&&  X#       _Sr!r#)*rPloggingrBpathlibrdefence360agent.internals.iaidrdefence360agent.api.serverr!defence360agent.api.server.eventsr defence360agent.contracts.configrr"defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr r "defence360agent.internals.the_sinkr defence360agent.model.icontactr $defence360agent.subsys.panels.cpanelr#defence360agent.subsys.panels.pleskr+defence360agent.subsys.panels.hosting_panelrdefence360agent.utilsrrrrrdefence360agent.utils.commonr getLoggerrprr r#r1r!rrs 999999//////777777;:::::766666;;;;;;777777555555DDDDDD-,,,,,  8 $ $ddddd[-dddddr!defence360agent/plugins/__pycache__/icontact_sender.cpython-311.pyc0000644000000000000000000001577500000000000022324 0ustar r_j"ddlZddlZddlZddlmZddlmZddlmZddl m Z ddl m Z m Z ddlmZddlmZmZdd lmZdd lmZdd lmZdd lmZdd lmZddlmZmZm Z m!Z!m"Z"ddl#m$Z$ej%e&Z'dZ(GddeeZ)dS)N)Path)IAIDTokenError)APIError) EventsAPI)CoreIContactMessageType) MessageType) MessageSink MessageSource)TheSink)IContactThrottle)cPanel)Plesk) HostingPanel) await_forcreate_task_and_log_exceptionsrecurring_checkretry_onScope)DAYcbKtd||tddS)Nz[Can't get recommendations for the dashboard due to iaid token error, reason: %s. Attempt %sdseconds)loggerwarningr)eis \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/icontact_sender.pyasync_log_on_errorr #s@ NN 3   cceZdZejjZejZ fdZ dZ dddZ de fdZdZeeed d d e eeed d e defdZeedZxZS)IContactSenderctj|i|g|_ttjdz |_dS)Nicontact_generic_notifications)super__init___tasksrrTMPDIR_notification_flag_path)selfargskwargs __class__s rr'zIContactSender.__init__1sG$)&)))    @ @ $$$r!c KdSN)r+loops r create_sinkzIContactSender.create_sink8s  r!Nuserc|K|dStj|||sdS|jtj||d{V}|rmtj||tj|ttj |}|j |d{VdSdS)Nr4) message_typeparamsr5)r7 timestamp template_args) r may_be_notified_panelnotifyrGENERICrefreshr IContactSentinttime_sinkprocess_message)r+r7r8 period_limitr5r: sent_messages r_send_icontact_messagez%IContactSender._send_icontact_message;s    F/       F"k00,41         ;  $\ = = = =&3)dikk**+L *,,\:: : : : : : : : : : ; ;r!sinkcK||_t|_|jjtjt jfvrt ||jg|_dSdSr0) rCrr<NAMErrrgeneric_notificationsr()r+r2rHs r create_sourcezIContactSender.create_sourceYs[ "nn ;  UZ8 8 8.$4DKKK 9 8r!c~K|jD]}|tj|jddid{VdS)Nreturn_exceptionsT)r(cancelasynciogather)r+tasks rshutdownzIContactSender.shutdowncsUK  D KKMMMMndkBTBBBBBBBBBBBr! rT)on_error max_triessilentlogreturnc Kg}|jr;|jjtzt jkr5t jd{V}|jdd|S)NiT)modeexist_ok) r*existsstatst_mtimerrBr notificationtouch)r+ notificationss rget_notificationsz IContactSender.get_notificationshs ,3355 J,1133>2    r!)__name__ __module__ __qualname__r ProcessingOrder ICONTACT_SENTPROCESSING_ORDERrAV_IM360SCOPEr'r3rGr rLrSrrrrrr listrdrrrK __classcell__)r.s@rr#r#-sQ"2@ NE         ;;;;;<gCCC X2&&&  X#       _Sr!r#)*rPloggingrBpathlibrdefence360agent.internals.iaidrdefence360agent.api.serverr!defence360agent.api.server.eventsr defence360agent.contracts.configrr"defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr r "defence360agent.internals.the_sinkr defence360agent.model.icontactr $defence360agent.subsys.panels.cpanelr#defence360agent.subsys.panels.pleskr+defence360agent.subsys.panels.hosting_panelrdefence360agent.utilsrrrrrdefence360agent.utils.commonr getLoggerrprr r#r1r!rrs 999999//////777777;:::::766666;;;;;;777777555555DDDDDD-,,,,,  8 $ $ddddd[-dddddr!defence360agent/plugins/__pycache__/idle_time_out.cpython-311.opt-1.pyc0000644000000000000000000000501600000000000022724 0ustar r_j|ddlmZddlmZddlmZddlmZddlm Z m Z m Z ee Z GddeZdS) ) getLogger) inactivity) SimpleRpc) MessageSink)clipfail_agent_servicerecurring_checkc eZdZdZdZdZdS)IdleTimeOutCheckc 4K||_tjr{tj|tttj dzdd|j |_ dSd|_ dS)N<)lowhigh)period) _looprSOCKET_ACTIVATIONrtrack reset_timer create_taskr rINACTIVITY_TIMEOUT_check_timeout_task)selfloops Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/idle_time_out.py create_sinkzIdleTimeOutCheck.create_sink s  &    ( ( * * *))!49qr ' DJJJDJJJchK|jr(|j|jd{VdSdS)N)rcancelrs rshutdownzIdleTimeOutCheck.shutdownsN :  J     *           rcKtdtjtjr*tdt dSdS)NzPeriodical check %s z Shutting down due to inactivity.)loggerinforr is_timeoutwarningrr"s rrzIdleTimeOutCheck._check_timeout"sd *J,<===   & & ( ( ! NN= > > >   ! !rN)__name__ __module__ __qualname__rr#rrrr r sA  !!!!!rr N)loggingrdefence360agent.apir defence360agent.contracts.configr!defence360agent.contracts.pluginsrdefence360agent.utilsrrr r)r%r r,rrr2s******666666999999KKKKKKKKKK 8  !!!!!{!!!!!rdefence360agent/plugins/__pycache__/idle_time_out.cpython-311.pyc0000644000000000000000000000501600000000000021765 0ustar r_j|ddlmZddlmZddlmZddlmZddlm Z m Z m Z ee Z GddeZdS) ) getLogger) inactivity) SimpleRpc) MessageSink)clipfail_agent_servicerecurring_checkc eZdZdZdZdZdS)IdleTimeOutCheckc 4K||_tjr{tj|tttj dzdd|j |_ dSd|_ dS)N<)lowhigh)period) _looprSOCKET_ACTIVATIONrtrack reset_timer create_taskr rINACTIVITY_TIMEOUT_check_timeout_task)selfloops Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/idle_time_out.py create_sinkzIdleTimeOutCheck.create_sink s  &    ( ( * * *))!49qr ' DJJJDJJJchK|jr(|j|jd{VdSdS)N)rcancelrs rshutdownzIdleTimeOutCheck.shutdownsN :  J     *           rcKtdtjtjr*tdt dSdS)NzPeriodical check %s z Shutting down due to inactivity.)loggerinforr is_timeoutwarningrr"s rrzIdleTimeOutCheck._check_timeout"sd *J,<===   & & ( ( ! NN= > > >   ! !rN)__name__ __module__ __qualname__rr#rrrr r sA  !!!!!rr N)loggingrdefence360agent.apir defence360agent.contracts.configr!defence360agent.contracts.pluginsrdefence360agent.utilsrrr r)r%r r,rrr2s******666666999999KKKKKKKKKK 8  !!!!!{!!!!!rdefence360agent/plugins/__pycache__/lve_utils_install.cpython-311.opt-1.pyc0000644000000000000000000000535000000000000023637 0ustar r_jRddlmZddlmZmZmZddlmZmZGddeZ dS)) MessageSink)check_run_outside_sandboxrecurring_checkRecurringCheckStop) is_lve_active has_lvectlc0eZdZdZdddZdZdZdZdS) LveUtilsAutoInstallera Install lve-utils package on CL with LVE automatically (according to DEF-11452) to provide tools to limit CPU/IO. Used tools: /usr/sbin/lvectl - provided by lve-utils package /bin/lve_suwrapper - provided by lve-wrappers package (which is a dependency of lve-utils) lve-utils package is installed by default on CL, but for some reason may not exist. i) check_periodc"||_d|_dSN) _check_period_task)selfr s ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/lve_utils_install.py__init__zLveUtilsAutoInstaller.__init__s) cK||_|jt|j|j|_dSr )_loop create_taskrr_install_lve_utils_if_neededr)rloops r create_sinkz!LveUtilsAutoInstaller.create_sinksY Z++  /OD. / /1       rcvK|j/|j|jd{Vd|_dSdSr )rcancelrs rshutdownzLveUtilsAutoInstaller.shutdown$sO : ! J     *       DJJJ " !rcKtsttstgdd{VdSdS)N)yumz-yinstallz lve-utils)rrrrrs rrz2LveUtilsAutoInstaller._install_lve_utils_if_needed*sy '$&& &|| ,555           rN)__name__ __module__ __qualname____doc__rrrrrrr r si  (,        rr N) !defence360agent.contracts.pluginsrdefence360agent.utilsrrr%defence360agent.utils.resource_limitsrrr r%rrr)s999999 LKKKKKKK*****K*****rdefence360agent/plugins/__pycache__/lve_utils_install.cpython-311.pyc0000644000000000000000000000535000000000000022700 0ustar r_jRddlmZddlmZmZmZddlmZmZGddeZ dS)) MessageSink)check_run_outside_sandboxrecurring_checkRecurringCheckStop) is_lve_active has_lvectlc0eZdZdZdddZdZdZdZdS) LveUtilsAutoInstallera Install lve-utils package on CL with LVE automatically (according to DEF-11452) to provide tools to limit CPU/IO. Used tools: /usr/sbin/lvectl - provided by lve-utils package /bin/lve_suwrapper - provided by lve-wrappers package (which is a dependency of lve-utils) lve-utils package is installed by default on CL, but for some reason may not exist. i) check_periodc"||_d|_dSN) _check_period_task)selfr s ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/lve_utils_install.py__init__zLveUtilsAutoInstaller.__init__s) cK||_|jt|j|j|_dSr )_loop create_taskrr_install_lve_utils_if_neededr)rloops r create_sinkz!LveUtilsAutoInstaller.create_sinksY Z++  /OD. / /1       rcvK|j/|j|jd{Vd|_dSdSr )rcancelrs rshutdownzLveUtilsAutoInstaller.shutdown$sO : ! J     *       DJJJ " !rcKtsttstgdd{VdSdS)N)yumz-yinstallz lve-utils)rrrrrs rrz2LveUtilsAutoInstaller._install_lve_utils_if_needed*sy '$&& &|| ,555           rN)__name__ __module__ __qualname____doc__rrrrrrr r si  (,        rr N) !defence360agent.contracts.pluginsrdefence360agent.utilsrrr%defence360agent.utils.resource_limitsrrr r%rrr)s999999 LKKKKKKK*****K*****rdefence360agent/plugins/__pycache__/myimunify.cpython-311.opt-1.pyc0000644000000000000000000000665500000000000022142 0ustar r_jddlZddlmZddlmZddlmZmZmZddl m Z ddl m Z ddl mZmZejeZGdd eeZdS) N)MyImunifyConfig) MessageType) MessageSink MessageSourceexpect)update_users_protection) hosting_panel) load_state save_statecneZdZdZdZdZdZdZee j de j fdZ dS) MyImunifyPluginctddp tj|_d|_d|_dSNr myimunify_enabled)r getrENABLED_previous_myimunify_status_loop_sinkselfs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/myimunify.py__init__zMyImunifyPlugin.__init__sD ( ) ) - -.A B B '& '  c8Ktdd|jidSr)r rrs rshutdownzMyImunifyPlugin.shutdowns0  $"A B     rc KdSN)rloops r create_sinkzMyImunifyPlugin.create_sink s  rc&K||_||_dSr)rr)rr sinks r create_sourcezMyImunifyPlugin.create_source#s  rcKtjd{V}t|j|ddd{VdS)NFT)force_config_update)r HostingPanel get_usersrr)rexisting_userss r_update_myimunify_usersz'MyImunifyPlugin._update_myimunify_users's|,9;;EEGGGGGGGG% J4            rmessagecKtj}|j}||_|r|s|d{V||kr/t j|d{VdSdS)N)r)rrrr*r r'switch_ui_config)rr+rprevious_statuss ron_config_updatez MyImunifyPlugin.on_config_update-s+39+<'  1_ 1..00 0 0 0 0 0 0 0  / /,..??"3@          0 /rN) __name__ __module__ __qualname__rrr!r$r*rr ConfigUpdater/rrrr r s          VK $%% k.F   &%   rr )logging defence360agent.contracts.configr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrrdefence360agent.myimunify.modelrdefence360agent.subsys.panelsr 'defence360agent.subsys.persistent_stater r getLoggerr0loggerr rrrr=s<<<<<<:::::: DCCCCC777777JJJJJJJJ  8 $ $)))))k=)))))rdefence360agent/plugins/__pycache__/myimunify.cpython-311.pyc0000644000000000000000000000665500000000000021203 0ustar r_jddlZddlmZddlmZddlmZmZmZddl m Z ddl m Z ddl mZmZejeZGdd eeZdS) N)MyImunifyConfig) MessageType) MessageSink MessageSourceexpect)update_users_protection) hosting_panel) load_state save_statecneZdZdZdZdZdZdZee j de j fdZ dS) MyImunifyPluginctddp tj|_d|_d|_dSNr myimunify_enabled)r getrENABLED_previous_myimunify_status_loop_sinkselfs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/myimunify.py__init__zMyImunifyPlugin.__init__sD ( ) ) - -.A B B '& '  c8Ktdd|jidSr)r rrs rshutdownzMyImunifyPlugin.shutdowns0  $"A B     rc KdSN)rloops r create_sinkzMyImunifyPlugin.create_sink s  rc&K||_||_dSr)rr)rr sinks r create_sourcezMyImunifyPlugin.create_source#s  rcKtjd{V}t|j|ddd{VdS)NFT)force_config_update)r HostingPanel get_usersrr)rexisting_userss r_update_myimunify_usersz'MyImunifyPlugin._update_myimunify_users's|,9;;EEGGGGGGGG% J4            rmessagecKtj}|j}||_|r|s|d{V||kr/t j|d{VdSdS)N)r)rrrr*r r'switch_ui_config)rr+rprevious_statuss ron_config_updatez MyImunifyPlugin.on_config_update-s+39+<'  1_ 1..00 0 0 0 0 0 0 0  / /,..??"3@          0 /rN) __name__ __module__ __qualname__rrr!r$r*rr ConfigUpdater/rrrr r s          VK $%% k.F   &%   rr )logging defence360agent.contracts.configr"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrrdefence360agent.myimunify.modelrdefence360agent.subsys.panelsr 'defence360agent.subsys.persistent_stater r getLoggerr0loggerr rrrr=s<<<<<<:::::: DCCCCC777777JJJJJJJJ  8 $ $)))))k=)))))rdefence360agent/plugins/__pycache__/ping.cpython-311.opt-1.pyc0000644000000000000000000000277100000000000021044 0ustar r_jDddlmZddlmZmZmZGddeeZdS)) MessageType) MessageSink MessageSourceexpectcXeZdZdZdZeejejdZ dS)SendPingcK||_dSN)_loop)selfloops Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/ping.py create_sinkzSendPing.create_sink s c&K||_||_dSr )r _sink)r r sinks r create_sourcezSendPing.create_source s  rclK|jtjd{VdSr )rprocess_messagerPing)r _s r send_pingzSendPing.send_pings=j(()9););<<<<<<<<<<r"s:::::: = = = = =}k = = = = =rdefence360agent/plugins/__pycache__/ping.cpython-311.pyc0000644000000000000000000000277100000000000020105 0ustar r_jDddlmZddlmZmZmZGddeeZdS)) MessageType) MessageSink MessageSourceexpectcXeZdZdZdZeejejdZ dS)SendPingcK||_dSN)_loop)selfloops Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/ping.py create_sinkzSendPing.create_sink s c&K||_||_dSr )r _sink)r r sinks r create_sourcezSendPing.create_source s  rclK|jtjd{VdSr )rprocess_messagerPing)r _s r send_pingzSendPing.send_pings=j(()9););<<<<<<<<<<r"s:::::: = = = = =}k = = = = =rdefence360agent/plugins/__pycache__/send_domain_list.cpython-311.opt-1.pyc0000644000000000000000000001254300000000000023420 0ustar r_j ddlZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZdd lmZmZmZejeZGd d e eZdS) N) AsyncIterator)int_from_envvar) DomainList)get_myimunify_users) MessageSink MessageSource)PanelException) HostingPanel)Scoperecurring_checksplit_for_chunkcdeZdZejZd dZdZdZdZ de de fdZ de e fd Zd ZdS) SendDomainListNcd|_|r ||_dStdtt jd|_dS)NIMUNIFY360_SEND_DOMAIN_PERIOD)days)_task_periodrintdatetime timedelta total_seconds)selfperiods ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_domain_list.py__init__zSendDomainList.__init__sY  !DLLL*/H&A...<<>>??DLLLc KdS)zMessageSink methodN)rloops r create_sinkzSendDomainList.create_sink(s rcK||_||_|jt|j|j|_dSN)_loop_sink create_taskr r_send_domain_listr)rr!sinks r create_sourcezSendDomainList.create_source+sT  Z++ A )ODL ) )$*@ A A C C   rcrK|j-d|jc|_}||d{VdSdSr$)rcancel)rts rshutdownzSendDomainList.shutdown3sG : ! $*MDJ HHJJJGGGGGGGGG " !r panel_typereturnc4ddd||S)Nprimaryalias)mainparked)get)rr/s r_panel_domain_type_to_imunifyz,SendDomainList._panel_domain_type_to_imunify9s(   #j* % % &rc *Kt}td|jg}t d{V}|D]}|d}t j|}||d{VD]K}|||j |j |d|j | |j dLtj}t|dD]} t!} || d<| | d<| WV dS) NzHostingsPanel: %susername myimunify_id)r9docrootnamesecuresite_user_iduidtypei ) chunk_size timestampdomains)r loggerinfoNAMErpwdgetpwnamget_user_domains_detailsappendr;domainpw_uidr7r?timer r) rhprBmyimunify_usersuserr9user_pwd domain_datarAchunkmsgs r_create_domain_list_msgz&SendDomainList._create_domain_list_msg?sZ ^^ '111 3 5 5555555#  DJ'H|H--H%'%@%@%J%JJJJJJJ   $,#.#6 + 2.2>.B' $ B B',!!       IKK $W>>>  E,,C(C "C NIIIII   rcK |23d{V}|j|d{V(6dS#t$r&}td|Yd}~dSd}~wwxYw)NzDomain list report skipped: %s)rTr&process_messager rCwarning)rrSes rr(z SendDomainList._send_domain_list[s @!99;; 6 6 6 6 6 6 6cj005555555555<;; @ @ @ NN;Q ? ? ? ? ? ? ? ? ? @s!AA#A A3 A..A3r$)__name__ __module__ __qualname__r AV_IM360SCOPErr"r*r.strr7rrrTr(r rrrrs NE!!!    &&&&&& }Z/H8@@@@@rr)rloggingrFrLtypingr defence360agent.contracts.configr"defence360agent.contracts.messagesr&defence360agent.contracts.myimunify_idr!defence360agent.contracts.pluginsrr"defence360agent.subsys.panels.baser +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr r r getLoggerrYrCrr rrris] :99999FFFFFF>=====DDDDDD  8 $ $E@E@E@E@E@[-E@E@E@E@E@rdefence360agent/plugins/__pycache__/send_domain_list.cpython-311.pyc0000644000000000000000000001254300000000000022461 0ustar r_j ddlZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZdd lmZmZmZejeZGd d e eZdS) N) AsyncIterator)int_from_envvar) DomainList)get_myimunify_users) MessageSink MessageSource)PanelException) HostingPanel)Scoperecurring_checksplit_for_chunkcdeZdZejZd dZdZdZdZ de de fdZ de e fd Zd ZdS) SendDomainListNcd|_|r ||_dStdtt jd|_dS)NIMUNIFY360_SEND_DOMAIN_PERIOD)days)_task_periodrintdatetime timedelta total_seconds)selfperiods ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_domain_list.py__init__zSendDomainList.__init__sY  !DLLL*/H&A...<<>>??DLLLc KdS)zMessageSink methodN)rloops r create_sinkzSendDomainList.create_sink(s rcK||_||_|jt|j|j|_dSN)_loop_sink create_taskr r_send_domain_listr)rr!sinks r create_sourcezSendDomainList.create_source+sT  Z++ A )ODL ) )$*@ A A C C   rcrK|j-d|jc|_}||d{VdSdSr$)rcancel)rts rshutdownzSendDomainList.shutdown3sG : ! $*MDJ HHJJJGGGGGGGGG " !r panel_typereturnc4ddd||S)Nprimaryalias)mainparked)get)rr/s r_panel_domain_type_to_imunifyz,SendDomainList._panel_domain_type_to_imunify9s(   #j* % % &rc *Kt}td|jg}t d{V}|D]}|d}t j|}||d{VD]K}|||j |j |d|j | |j dLtj}t|dD]} t!} || d<| | d<| WV dS) NzHostingsPanel: %susername myimunify_id)r9docrootnamesecuresite_user_iduidtypei ) chunk_size timestampdomains)r loggerinfoNAMErpwdgetpwnamget_user_domains_detailsappendr;domainpw_uidr7r?timer r) rhprBmyimunify_usersuserr9user_pwd domain_datarAchunkmsgs r_create_domain_list_msgz&SendDomainList._create_domain_list_msg?sZ ^^ '111 3 5 5555555#  DJ'H|H--H%'%@%@%J%JJJJJJJ   $,#.#6 + 2.2>.B' $ B B',!!       IKK $W>>>  E,,C(C "C NIIIII   rcK |23d{V}|j|d{V(6dS#t$r&}td|Yd}~dSd}~wwxYw)NzDomain list report skipped: %s)rTr&process_messager rCwarning)rrSes rr(z SendDomainList._send_domain_list[s @!99;; 6 6 6 6 6 6 6cj005555555555<;; @ @ @ NN;Q ? ? ? ? ? ? ? ? ? @s!AA#A A3 A..A3r$)__name__ __module__ __qualname__r AV_IM360SCOPErr"r*r.strr7rrrTr(r rrrrs NE!!!    &&&&&& }Z/H8@@@@@rr)rloggingrFrLtypingr defence360agent.contracts.configr"defence360agent.contracts.messagesr&defence360agent.contracts.myimunify_idr!defence360agent.contracts.pluginsrr"defence360agent.subsys.panels.baser +defence360agent.subsys.panels.hosting_panelr defence360agent.utilsr r r getLoggerrYrCrr rrris] :99999FFFFFF>=====DDDDDD  8 $ $E@E@E@E@E@[-E@E@E@E@E@rdefence360agent/plugins/__pycache__/send_server_config.cpython-311.opt-1.pyc0000644000000000000000000004705200000000000023754 0ustar r_j,ddlZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZmZmZmZmZmZmZmZddlmZdd lmZdd lmZm Z m!Z!dd l"m#Z#dd l$m%Z%m&Z&dd l'm(Z(ddl)m*Z*ddl+m,Z,ddl-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4ddl5m6Z6m7Z7ddl8m9Z9dZ:e e;ZeddZ?e2dZ@e2dZAe2dZBe2dZCe2dZDe2dZEe2dZFd ZGe2d!ZHd"ZId#e fd$ZJGd%d&ee ZKd#e eLeMffd'ZNd(eLd#eMfd)ZOd*ZPd#eQfd+ZRd#eLfd,ZSd#eLfd-ZTd#eLfd.ZUdS)/N) lru_cache) getLogger)Path)DictList)sentry) ConfigFileCoreCustomBillingConfigMalwareMalwareSignatures SystemConfigint_from_envvarFREEMIUM_FEATURE_FLAG) LicenseCLN) MessageType) MessageSink MessageSourceexpect)get_myimunify_users)$is_native_feature_management_enabled&is_native_feature_management_supported)IndependentAgentIDAPI) HostingPanel)cPanel)log_error_and_ignorerecurring_checksafe_cancel_taskScopestub_unexpected_errorsafe_runsystem_packages_info) load_state save_state) WhmcsConf)z/var/imunify360/imunify360.dbz!/var/imunify360/imunify360.db-shmz!/var/imunify360/imunify360.db-walz/var/imunify360/gw.dir/>ai-bolit imunify-ui imunify-coreimunify-commonimunify360-pamimunify-releaseimunify360-venvalt-php-internalimunify-notifierimunify-patchmanimunify360-ossecalt-php-hyperscanimunify-antivirusalt-common-releaseimunify-realtime-avimunify-wp-securityimunify360-firewallimunify360-php-i360app-version-detectorcloudlinux-backup-utilsimunify360-ossec-serverimunify-auditd-log-readerimunify-realtime-av-imrt2imunify360-webshield-bundle imunify360-unified-access-logger rustbolit minidaemonc|td5}|cdddS#1swxYwYdS)Nz /proc/cpuinfo)openread)fs _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_server_config.py read_cpu_inforF^s| o  !vvxxs 155)maxsizect}tjd|tj}g}i}|D]-\}}|dkr|rd|vr||i}|||<.|||S)Nz^(.*?)[ ]*:[ ]*(.*)$)flags processor)rFrefindallMappend)texttuplesrescurrentkeyvalues rE get_cpu_inforVcs ??D Z2D E E EFCG U +   ;'11 7### JJw Jci}tD]H}|d|dx}|vr&t|dd||<It|S)Nz physical idrKz cpu coresrG)rVgetintsumvalues) physical_idsrK physical_ids rE get_cpu_coresr_tsL!^^KK $== +8NOO OK  ),IMM+q,I,I(J(JL % |""$$ % %%rWctd}d|dp|d|dp|dS)Nrz{} {}z model name ProcessorrJFeatures)rVformatrY)rKs rEget_cpu_model_and_flagsrdsXq!I >> l##=y'= g7)J"7  rWc:K|d{VSN)versionhps rEget_hosting_panel_versionrjs&      rWc:K|d{VSrf) users_countrhs rEget_users_amountrms(!! ! ! ! ! ! !!rWcTKt|d{VSrf)lenget_domain_to_ownerrhs rEget_domains_amountrqs2 R++-------- . ..rWctjtjr6t tjtjSdSrf)ospathexistsr AI_BOLIT_HOSTERrZgetmtimerWrEget_malware_db_update_timerysK w~~'788H27##$5$EFFGGGHHrWcZKtd{Vrtd{VSdSrf)rrrxrWrE get_nfm_stater{sN 3 5 5555555<9;;;;;;;;;<">3@??-B-B-B-B-B-B+--*,'.. !+ + &J%K%K  rWreturnc,Kt} t|d{V}n8#t$r+tdt}YnwxYwt jdtj }ttj  |D]K}|jj|vr;t!|jjd||jj<L|S)zv Return dict that includes users config values that are explicitly set in the corresponding config files. Nz(Failed to get the list of panel's users.*)usernameF) normalize)dict frozenset get_users Exceptionlogger exceptionrsrtjoinr USER_CONFIG_FILE_NAMEr USER_CONFDIRglobparentnamer config_to_dict)riresult current_users users_conf userconf_files rEget_users_configsrs VVF$! "6"6"6"6"6"677 $$$CDDD! $c4#=>>Jd/0055jAA..   $ 5 50:&-2111nun-- =', - Ms':2A/.A/ceZdZdZejZd dZdZe e j e dZ dZdZdZd ZdS) SendServerConfigz This plugin is to provide central server with different server metrics. Message is sent on plugin creation, and then every :period: seconds Ncd|_d|_|r ||_dStdt t jddz |_dS)N$IMUNIFY360_SEND_SERVER_CONFIG_PERIODrG)days)_task_last_send_time_periodrrZdatetime timedelta total_seconds)selfperiods rE__init__zSendServerConfig.__init__sf #  !DLLL*6H&A...<<>>BCCDLLLrWc KdS)zMessageSink methodNrx)rloops rE create_sinkzSendServerConfig.create_sinks rWcK|j d|_dSt|dtsdS|d|jr;|d|_|j|dSdS)Nrconf timestamp)r isinstancermodified_since_loop create_task_send_server_config)rmessages rEon_config_update_messagez)SendServerConfig.on_config_update_messages   '#$D F'&/<88  F 6? ) )$*> ? ? ?#*;#7D J " "4#;#;#=#= > > > > > ? ?rWcK||_||_|jt|j|j|_dSrf)r_sinkrrrrr)rrsinks rE create_sourcezSendServerConfig.create_sourcesT  Z++ C )ODL ) )$*B C C E E   rWcdK|j&d|jc|_}t|d{VdSdSrf)rr)rts rEshutdownzSendServerConfig.shutdown sK : ! $*MDJ"1%% % % % % % % % % % " !rWcKtjt}t}|r||d<t }t j}|tj |t|d{V|j tj krtd{V|d<tj|d<t j|d<t#d{Vt%d{Vt'd{Vd|d<t)t-z|d<t/|d{V|d <t1t2d{V|d <t5t6d{V|d <|d |dd <|d|dd<t;dd|dd<t=dddi|S)N)uname diskstatsusersiaidstatus_license) uptime_sincedevicesmac system_infoagent_global_configagent_users_configspathscomponents_versions upgrade_urlz$CUSTOM_BILLING.effective_upgrade_urlupgrade_url_360z(CUSTOM_BILLING.effective_upgrade_url_360 doctor_keyzCORE.doctor_report)r ServerConfig _uname_info _diskstatrr license_infoupdatertagsrNAMErrrget_iaidis_valid_uptime_blkid _mac_addressr rr r_get_path_sizesCH_PATHSr"PACKAGES_TO_REPORTrYr#r$)rmsgdiskstatrirs rE_create_server_config_msgz*SendServerConfig._create_server_config_msgs&[]];;;;;  ('C  ^^!.00  6;==!!! ,R00000000111 7fk ! !!4!6!6666666CL+466F * 3 5 5 ")))OOOOOO#XX~~~~~~%''''''  M LL ' ' ) )!##2244 5 !",=R+@+@%@%@%@%@%@%@ !",X66666666G +? , , & & & & & &  !"   ] + + !" 2   . / / !" 6 size mapping for *paths*. Send -errno on error. z!Can't get size for %s, reason: %sN) maprsfspathrtisdir_compute_dir_sizegetsizeOSErrorrwarningerrno)rsizesrtsizees rErrBs EBIu%%   w}}T"" -(..wt,, E$KK  # # # NN>a H H H7(E$KKKKKK # LsAA66 B1'B,,B1directory_pathcd}dtfd}tj||dD]b\}}}|D]Y}tj||} |tj|z }F#t$r}|d}~wwxYwc|S)Nrerrc|rfrx)r s rE_onerrorz#_compute_dir_size.._onerrorYs rWF)onerror followlinks)rrswalkrtrr) r  total_sizer root_dirsfiles file_name file_pathrs rErrVsJg!geeU  I T955I bgooi888        s"A33 B=A??Bc td5}|cdddS#1swxYwYdS#t$r3}tdt |Yd}~dSd}~wwxYw)Nz/proc/diskstatszCan't get diskstat: %s)rBrCrrrstr)rDrs rErrhs9 # $ $ 6688                   999/Q8888888889s-?2 ?6?6? A< (A77A<c^ttdtjS)N)sysnamenodenamereleasergmachine)rziprsrrxrWrErrps-  D HJJ    rWc4Ktddgd{VS)zSystem up sinceuptimez--sinceNr!rxrWrErrys+8Y/00 0 0 0 0 0 00rWc2Ktdgd{VS)z8Executes utility to locate/print block device attributesblkidNr!rxrWrErr~s(7)$$ $ $ $ $ $ $$rWcKtjtjdddS)zOMAC address in formatted way, like it specifies in /sys/class/net/*/addressbig:)binasciihexlifyuuidgetnodeto_bytesdecoderxrWrErrs=  DLNN33Au==s C C J J L LLrW)Vr(rr}rsrLr* functoolsrloggingrpathlibrtypingrrdefence360agent.contractsr defence360agent.contracts.configr r r r r rrr!defence360agent.contracts.licenser"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrr&defence360agent.contracts.myimunify_idr*defence360agent.feature_management.controlrrdefence360agent.internals.iaidr+defence360agent.subsys.panels.hosting_panelr$defence360agent.subsys.panels.cpanelrdefence360agent.utilsrrrrr r!r"'defence360agent.subsys.persistent_stater#r$defence360agent.utils.whmcsr%rrrrrFrVr_rdrjrmrqryr{rrrrrrrZrrrrrrrrrxrWrEr?s ,,,,,,                    988888:::::: GFFFFFA@@@@@DDDDDD777777KJJJJJJJ111111  8  >  1    &&&"""///HHH <<< (((&4(d d d d d {Md d d NDcN(cc$999T1s1111 %c%%%% MCMMMMMMrWdefence360agent/plugins/__pycache__/send_server_config.cpython-311.pyc0000644000000000000000000004705200000000000023015 0ustar r_j,ddlZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZmZmZmZmZmZmZmZddlmZdd lmZdd lmZm Z m!Z!dd l"m#Z#dd l$m%Z%m&Z&dd l'm(Z(ddl)m*Z*ddl+m,Z,ddl-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4ddl5m6Z6m7Z7ddl8m9Z9dZ:e e;ZeddZ?e2dZ@e2dZAe2dZBe2dZCe2dZDe2dZEe2dZFd ZGe2d!ZHd"ZId#e fd$ZJGd%d&ee ZKd#e eLeMffd'ZNd(eLd#eMfd)ZOd*ZPd#eQfd+ZRd#eLfd,ZSd#eLfd-ZTd#eLfd.ZUdS)/N) lru_cache) getLogger)Path)DictList)sentry) ConfigFileCoreCustomBillingConfigMalwareMalwareSignatures SystemConfigint_from_envvarFREEMIUM_FEATURE_FLAG) LicenseCLN) MessageType) MessageSink MessageSourceexpect)get_myimunify_users)$is_native_feature_management_enabled&is_native_feature_management_supported)IndependentAgentIDAPI) HostingPanel)cPanel)log_error_and_ignorerecurring_checksafe_cancel_taskScopestub_unexpected_errorsafe_runsystem_packages_info) load_state save_state) WhmcsConf)z/var/imunify360/imunify360.dbz!/var/imunify360/imunify360.db-shmz!/var/imunify360/imunify360.db-walz/var/imunify360/gw.dir/>ai-bolit imunify-ui imunify-coreimunify-commonimunify360-pamimunify-releaseimunify360-venvalt-php-internalimunify-notifierimunify-patchmanimunify360-ossecalt-php-hyperscanimunify-antivirusalt-common-releaseimunify-realtime-avimunify-wp-securityimunify360-firewallimunify360-php-i360app-version-detectorcloudlinux-backup-utilsimunify360-ossec-serverimunify-auditd-log-readerimunify-realtime-av-imrt2imunify360-webshield-bundle imunify360-unified-access-logger rustbolit minidaemonc|td5}|cdddS#1swxYwYdS)Nz /proc/cpuinfo)openread)fs _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/send_server_config.py read_cpu_inforF^s| o  !vvxxs 155)maxsizect}tjd|tj}g}i}|D]-\}}|dkr|rd|vr||i}|||<.|||S)Nz^(.*?)[ ]*:[ ]*(.*)$)flags processor)rFrefindallMappend)texttuplesrescurrentkeyvalues rE get_cpu_inforVcs ??D Z2D E E EFCG U +   ;'11 7### JJw Jci}tD]H}|d|dx}|vr&t|dd||<It|S)Nz physical idrKz cpu coresrG)rVgetintsumvalues) physical_idsrK physical_ids rE get_cpu_coresr_tsL!^^KK $== +8NOO OK  ),IMM+q,I,I(J(JL % |""$$ % %%rWctd}d|dp|d|dp|dS)Nrz{} {}z model name ProcessorrJFeatures)rVformatrY)rKs rEget_cpu_model_and_flagsrdsXq!I >> l##=y'= g7)J"7  rWc:K|d{VSN)versionhps rEget_hosting_panel_versionrjs&      rWc:K|d{VSrf) users_countrhs rEget_users_amountrms(!! ! ! ! ! ! !!rWcTKt|d{VSrf)lenget_domain_to_ownerrhs rEget_domains_amountrqs2 R++-------- . ..rWctjtjr6t tjtjSdSrf)ospathexistsr AI_BOLIT_HOSTERrZgetmtimerWrEget_malware_db_update_timerysK w~~'788H27##$5$EFFGGGHHrWcZKtd{Vrtd{VSdSrf)rrrxrWrE get_nfm_stater{sN 3 5 5555555<9;;;;;;;;;<">3@??-B-B-B-B-B-B+--*,'.. !+ + &J%K%K  rWreturnc,Kt} t|d{V}n8#t$r+tdt}YnwxYwt jdtj }ttj  |D]K}|jj|vr;t!|jjd||jj<L|S)zv Return dict that includes users config values that are explicitly set in the corresponding config files. Nz(Failed to get the list of panel's users.*)usernameF) normalize)dict frozenset get_users Exceptionlogger exceptionrsrtjoinr USER_CONFIG_FILE_NAMEr USER_CONFDIRglobparentnamer config_to_dict)riresult current_users users_conf userconf_files rEget_users_configsrs VVF$! "6"6"6"6"6"677 $$$CDDD! $c4#=>>Jd/0055jAA..   $ 5 50:&-2111nun-- =', - Ms':2A/.A/ceZdZdZejZd dZdZe e j e dZ dZdZdZd ZdS) SendServerConfigz This plugin is to provide central server with different server metrics. Message is sent on plugin creation, and then every :period: seconds Ncd|_d|_|r ||_dStdt t jddz |_dS)N$IMUNIFY360_SEND_SERVER_CONFIG_PERIODrG)days)_task_last_send_time_periodrrZdatetime timedelta total_seconds)selfperiods rE__init__zSendServerConfig.__init__sf #  !DLLL*6H&A...<<>>BCCDLLLrWc KdS)zMessageSink methodNrx)rloops rE create_sinkzSendServerConfig.create_sinks rWcK|j d|_dSt|dtsdS|d|jr;|d|_|j|dSdS)Nrconf timestamp)r isinstancermodified_since_loop create_task_send_server_config)rmessages rEon_config_update_messagez)SendServerConfig.on_config_update_messages   '#$D F'&/<88  F 6? ) )$*> ? ? ?#*;#7D J " "4#;#;#=#= > > > > > ? ?rWcK||_||_|jt|j|j|_dSrf)r_sinkrrrrr)rrsinks rE create_sourcezSendServerConfig.create_sourcesT  Z++ C )ODL ) )$*B C C E E   rWcdK|j&d|jc|_}t|d{VdSdSrf)rr)rts rEshutdownzSendServerConfig.shutdown sK : ! $*MDJ"1%% % % % % % % % % % " !rWcKtjt}t}|r||d<t }t j}|tj |t|d{V|j tj krtd{V|d<tj|d<t j|d<t#d{Vt%d{Vt'd{Vd|d<t)t-z|d<t/|d{V|d <t1t2d{V|d <t5t6d{V|d <|d |dd <|d|dd<t;dd|dd<t=dddi|S)N)uname diskstatsusersiaidstatus_license) uptime_sincedevicesmac system_infoagent_global_configagent_users_configspathscomponents_versions upgrade_urlz$CUSTOM_BILLING.effective_upgrade_urlupgrade_url_360z(CUSTOM_BILLING.effective_upgrade_url_360 doctor_keyzCORE.doctor_report)r ServerConfig _uname_info _diskstatrr license_infoupdatertagsrNAMErrrget_iaidis_valid_uptime_blkid _mac_addressr rr r_get_path_sizesCH_PATHSr"PACKAGES_TO_REPORTrYr#r$)rmsgdiskstatrirs rE_create_server_config_msgz*SendServerConfig._create_server_config_msgs&[]];;;;;  ('C  ^^!.00  6;==!!! ,R00000000111 7fk ! !!4!6!6666666CL+466F * 3 5 5 ")))OOOOOO#XX~~~~~~%''''''  M LL ' ' ) )!##2244 5 !",=R+@+@%@%@%@%@%@%@ !",X66666666G +? , , & & & & & &  !"   ] + + !" 2   . / / !" 6 size mapping for *paths*. Send -errno on error. z!Can't get size for %s, reason: %sN) maprsfspathrtisdir_compute_dir_sizegetsizeOSErrorrwarningerrno)rsizesrtsizees rErrBs EBIu%%   w}}T"" -(..wt,, E$KK  # # # NN>a H H H7(E$KKKKKK # LsAA66 B1'B,,B1directory_pathcd}dtfd}tj||dD]b\}}}|D]Y}tj||} |tj|z }F#t$r}|d}~wwxYwc|S)Nrerrc|rfrx)r s rE_onerrorz#_compute_dir_size.._onerrorYs rWF)onerror followlinks)rrswalkrtrr) r  total_sizer root_dirsfiles file_name file_pathrs rErrVsJg!geeU  I T955I bgooi888        s"A33 B=A??Bc td5}|cdddS#1swxYwYdS#t$r3}tdt |Yd}~dSd}~wwxYw)Nz/proc/diskstatszCan't get diskstat: %s)rBrCrrrstr)rDrs rErrhs9 # $ $ 6688                   999/Q8888888889s-?2 ?6?6? A< (A77A<c^ttdtjS)N)sysnamenodenamereleasergmachine)rziprsrrxrWrErrps-  D HJJ    rWc4Ktddgd{VS)zSystem up sinceuptimez--sinceNr!rxrWrErrys+8Y/00 0 0 0 0 0 00rWc2Ktdgd{VS)z8Executes utility to locate/print block device attributesblkidNr!rxrWrErr~s(7)$$ $ $ $ $ $ $$rWcKtjtjdddS)zOMAC address in formatted way, like it specifies in /sys/class/net/*/addressbig:)binasciihexlifyuuidgetnodeto_bytesdecoderxrWrErrs=  DLNN33Au==s C C J J L LLrW)Vr(rr}rsrLr* functoolsrloggingrpathlibrtypingrrdefence360agent.contractsr defence360agent.contracts.configr r r r r rrr!defence360agent.contracts.licenser"defence360agent.contracts.messagesr!defence360agent.contracts.pluginsrrr&defence360agent.contracts.myimunify_idr*defence360agent.feature_management.controlrrdefence360agent.internals.iaidr+defence360agent.subsys.panels.hosting_panelr$defence360agent.subsys.panels.cpanelrdefence360agent.utilsrrrrr r!r"'defence360agent.subsys.persistent_stater#r$defence360agent.utils.whmcsr%rrrrrFrVr_rdrjrmrqryr{rrrrrrrZrrrrrrrrrxrWrEr?s ,,,,,,                    988888:::::: GFFFFFA@@@@@DDDDDD777777KJJJJJJJ111111  8  >  1    &&&"""///HHH <<< (((&4(d d d d d {Md d d NDcN(cc$999T1s1111 %c%%%% MCMMMMMMrWdefence360agent/plugins/__pycache__/service_manager.cpython-311.opt-1.pyc0000644000000000000000000000766200000000000023245 0ustar r_j|dZddlZddlZddlmZddlmZmZeje Z Gddej Z dS)zBase service manager plugin. Provides the shared start/stop/enable/disable logic that product-specific service managers (imav, im360) inherit from. N)utils)messagespluginsceZdZdZdZdZejej j dej j fdZ e j d dZdS) BaseServiceManageruBase service manager: start/stop services based on config changes. Subclasses populate ``_services`` (list of async check callables) and ``_units`` (dict of name → unitctl) in their ``__init__``. cRtj|_g|_i|_dSN)asyncioLock_lock _services_units)selfs \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/service_manager.py__init__zBaseServiceManager.__init__s!\^^  c>K|jD]}|d{VdSr )r )rservices r!_ensure_consistent_services_statez4BaseServiceManager._ensure_consistent_services_states:~  G'))OOOOOOOO  rmessage_ignoredcK|j4d{V|d{Vdddd{VdS#1d{VswxYwYdSr )r r)rrs ron_config_updatez#BaseServiceManager.on_config_update s: ; ; ; ; ; ; ; ;88:: : : : : : : : ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;s> A AFcK|d{V}||ur|rTtd||dd{Vtd|dStd||dd{Vtd|dS|r9|r9|d{Vtd|dSdSdS)NzA%s is enabled in the config but it is not running. Enabling it...T)nowz Enabled %szB%s is not enabled in the config but it is running. Disabling it...z Disabled %sz#Reloading %s after config update...) is_activeloggerinfoenabledisablereload)runitctl service_nameshould_be_runningr is_runnings r_ensure_service_statusz)BaseServiceManager._ensure_service_status'si#,,........ . . .  9 /  nnn......... L,77777 0  oo$o///////// M<88888 f nn&&&&&&&&& 9<    rN)F)__name__ __module__ __qualname____doc__rrrexpectr MessageType ConfigUpdaterrlog_error_and_ignorer%rrrrs  W^H(566;'3@;;;76;  U!!?D"!rr) r)r loggingdefence360agentrdefence360agent.contractsrr getLoggerr&r MessageSinkrr.rrr4s !!!!!!77777777  8 $ $22222,22222rdefence360agent/plugins/__pycache__/service_manager.cpython-311.pyc0000644000000000000000000000766200000000000022306 0ustar r_j|dZddlZddlZddlmZddlmZmZeje Z Gddej Z dS)zBase service manager plugin. Provides the shared start/stop/enable/disable logic that product-specific service managers (imav, im360) inherit from. N)utils)messagespluginsceZdZdZdZdZejej j dej j fdZ e j d dZdS) BaseServiceManageruBase service manager: start/stop services based on config changes. Subclasses populate ``_services`` (list of async check callables) and ``_units`` (dict of name → unitctl) in their ``__init__``. cRtj|_g|_i|_dSN)asyncioLock_lock _services_units)selfs \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/service_manager.py__init__zBaseServiceManager.__init__s!\^^  c>K|jD]}|d{VdSr )r )rservices r!_ensure_consistent_services_statez4BaseServiceManager._ensure_consistent_services_states:~  G'))OOOOOOOO  rmessage_ignoredcK|j4d{V|d{Vdddd{VdS#1d{VswxYwYdSr )r r)rrs ron_config_updatez#BaseServiceManager.on_config_update s: ; ; ; ; ; ; ; ;88:: : : : : : : : ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;s> A AFcK|d{V}||ur|rTtd||dd{Vtd|dStd||dd{Vtd|dS|r9|r9|d{Vtd|dSdSdS)NzA%s is enabled in the config but it is not running. Enabling it...T)nowz Enabled %szB%s is not enabled in the config but it is running. Disabling it...z Disabled %sz#Reloading %s after config update...) is_activeloggerinfoenabledisablereload)runitctl service_nameshould_be_runningr is_runnings r_ensure_service_statusz)BaseServiceManager._ensure_service_status'si#,,........ . . .  9 /  nnn......... L,77777 0  oo$o///////// M<88888 f nn&&&&&&&&& 9<    rN)F)__name__ __module__ __qualname____doc__rrrexpectr MessageType ConfigUpdaterrlog_error_and_ignorer%rrrrs  W^H(566;'3@;;;76;  U!!?D"!rr) r)r loggingdefence360agentrdefence360agent.contractsrr getLoggerr&r MessageSinkrr.rrr4s !!!!!!77777777  8 $ $22222,22222rdefence360agent/plugins/__pycache__/wordpress.cpython-311.opt-1.pyc0000644000000000000000000012244600000000000022141 0ustar r_jddlZddlZddlZddlmZddlmZddlmZddl m Z m Z m Z m Z mZddlmZddlmZddlmZdd lmZmZmZdd lmZdd lmZmZmZdd lm Z m!Z!m"Z"m#Z#dd l$m%Z%ddl&m'Z'ddl(m)Z)ddl*m+Z,ddl*m-Z-ddl.m/Z/ddl0m1Z1ddl2m3Z3ddl4m5Z5m6Z6ddl7m8Z8m9Z9m:Z:ddl;mZ>m?Z?m@Z@ddlAmBZBddlCmDZDejEeFZGede jHZIede jHZJede jHZKede jHZLedZMed ZNeMd!z ZOeMd"z ZPe!jQd#d$d%ZRd&eSd'eTfd(ZUGd)d*eeZVdS)+N)suppress)Path) Coroutine)ANTIVIRUS_MODEConfigValidationError SystemConfig UserConfig Wordpress) HookEvent) LicenseCLN) MessageType) MessageSink MessageSourceexpect) hosting_panel) load_stateregister_lock_file save_state)Scopeimporterrecurring_checksystem_packages_info) check_lock)IndependentAgentIDAPI)DAY)cli)plugin)_prepare_ai_bot_settings)resolve_ai_bot_protection) tls_check)WPSite WordpressSite)get_sites_by_pathget_sites_for_userget_installed_sites)is_secret_expired rotate_secret)ChangelogProcessorIncidentCollectorIncidentSender)update_disabled_rules_on_sites)delete_old_wordpress_incidentsz wp-gen-authzwp-site-processzwp-plugin-statszwp-license-reconvergez-/etc/sysconfig/imunify360/imunify360.config.dzF/opt/imunify360/venv/share/imunify360/11_on_first_install_wp_av.configz 11_on_first_install_wp_av.configz.11_on_first_install_wp_av.flagzimav.malwarelib.model MalwareHit)modulenamedefaultstarted_timestampreturnc|ttdgSt|S)z Get malware hits cleaned since the given timestamp with lazy import fallback. Returns empty list if imav.malwarelib is not available. Nz;imav.malwarelib not available, returning empty cleaned hits) _MalwareHitloggerdebug cleaned_since)r1s V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/wordpress.py_get_cleaned_malware_hitsr9_s?  I     $ $%6 7 77ceZdZejZdZdZdZdZ dZ dZ dZ dZ d ejfd Zd%d efd ZdZeedeedZeeddeedZeeddedZdZdZdZdZ dZ!dZ"e#e$j%dZ&e#e$j'dZ(e#e$j'dZ)eedde*dZ+d Z,e#e$j'd!Z-e#e.j/d"Z0e#e.j1d#Z2d$S)&ImunifySecurityPlugincd|_d|_td}|d|_|d}||n t j|_tj |_ tj |_ i|_ tj|_tj|_d|_d|_d|_d|_t-|_t1|_t5|_t9|_d|_d|_d|_ dS)Nr< installedenabled)!_loop_sinkrgetinstallation_completedr SECURITY_PLUGIN_ENABLEDlast_config_valuer_get_global_waf_enabled_last_waf_enabled_get_waf_default_last_waf_default_last_user_waf_enabled_get_global_ai_bot_protection_last_ai_bot_protection$_get_global_ai_bot_protection_preset_last_ai_bot_protection_preset_last_license_typeinstallation_task deleting_taskinstall_and_update_tasksetfreshly_installed_sitesr)incident_collectorr*incident_senderr(changelog_processor_site_processing_task _stats_task_license_reconverge_task)selfstatepersisted_enableds r8__init__zImunifySecurityPlugin.__init__ps/  233&+ii &<&<#"IIi00!,  2  "(!?!A!A!'!8!:!:>@#(.'K'M'M$  7 9 9 + #'6:26<@$47EE$#4"5"5-//#5#7#7 :>"04=A%%%r:c KdSN)r[loops r8 create_sinkz!ImunifySecurityPlugin.create_sinks  r:c\K||_||_|j||_|j||_|j||_|j| |_ tr| d{Vntd|d{VdS)NT) missing_ok)r@rA create_taskrefresh_auth_files_update_auth_taskprocess_wordpress_sitesrX send_statsrYreconverge_license_typerZr_apply_first_install_configFIRST_INSTALL_FLAGunlink _recover_installation_on_startup)r[rbsinks r8 create_sourcez#ImunifySecurityPlugin.create_sources'  !%!7!7  # # % %" " &*Z%;%;  ( ( * *& & " :11$//2C2CDD(, (>(>  ( ( * *) ) %  72244 4 4 4 4 4 4 4 4  % % % 6 6 63355555555555r:c,K|js tjsdStdd|_|tj|j d{V|j !|j |j dSdS)a Self-heal when the installation state was lost. If the feature is enabled but installation_completed is falsy (state file missing, earlier install interrupted, etc.), manage_plugin_installation can never recover: its True == True guard always returns early. Trigger install_everywhere once per restart to repopulate the wordpress_site table and flip the flag. NzXInstallation state is missing while feature is enabled; triggering startup self-recoveryTrp) rCr rDr5inforEprocess_installationrinstall_everywhererArPadd_done_callback_mark_installation_doner[s r8roz6ImunifySecurityPlugin._recover_installation_on_startups  ' 4  F  /   "&''  %4: 6 6 6           ! -  " 4 4,      . -r:cfKtsdStjd{VdkrKt t}t dt dS)Ni) rmexistsr HostingPanel users_countFIRST_INSTALL_CONFIG_PATH write_textFIRST_INSTALL_CONFIG_FILE read_textchmodrn)r[_s r8rlz1ImunifySecurityPlugin._apply_first_install_configs!((**  F+--99;; ; ; ; ; ; ;q @ @)44)3355A & + +E 2 2 2!!#####r:chK|j|jd{V|jr&|j|jd{V|jr&|j|jd{V|jr(|j|jd{VdSdSr`)rhcancelrXrYrZrys r8shutdownzImunifySecurityPlugin.shutdowns %%'''$$$$$$$$  % -  & - - / / /, , , , , , , ,   #   # # % % %" " " " " " " "  ( 0  ) 0 0 2 2 2/ / / / / / / / / / 0 0r:ct||std|dSt||}|duo)| o| S)NzUnknown task '%s'F)hasattrr5errorgetattrdone cancelled)r[task_attr_nametasks r8_task_in_progressz'ImunifySecurityPlugin._task_in_progresssit^,,  LL,n = = =5t^,,4L OLDNNr?)rrCrErys r8_save_installation_statez.ImunifySecurityPlugin._save_installation_states7 #!81       r:rcH|rtddS|}|td|dS|jstddSd|_|dS)NzInstallation task was cancelledzInstallation task failed: %sz>Feature was disabled during installation, skipping flag updateT)rr5rt exceptionrrErCr)r[rexcs r8rxz-ImunifySecurityPlugin._mark_installation_dones >>    KK9 : : : Fnn ? LL7 = = = F%  KK'    F&*# %%'''''r:FcorocK|dr\|r|dS|jr=|j |jd{Vn#tj$rYnwxYw|dr0t d|dSt j||_ dS)NrQrPzInstallation is already running) rcloserQrasyncioCancelledErrorr5warningrfrP)r[r for_new_sitess r8ruz*ImunifySecurityPlugin.process_installations  ! !/ 2 2   ! "))+++,,,,,,,,,-D  ! !"5 6 6  NN< = = = JJLLL F!(!4T!:!:s AA10A1cPK|drD|jr=|j |jd{Vn#tj$rYnwxYw|drt ddStj||_dS)NrPrQzDeleting is already running) rrPrrrr5rrfrQ)r[rs r8process_deletingz&ImunifySecurityPlugin.process_deleting$s  ! !"5 6 6 % &--///000000000-D  ! !/ 2 2  NN8 9 9 9 F$066s AAAT)check_period_firstcheck_lock_period lock_filecKtrtd{Vtj|jd{VdSNrs)r&r'rupdate_auth_everywhererArys r8rgz(ImunifySecurityPlugin.refresh_auth_files3s`    "// ! ! ! ! ! ! !+<<<<<<<<<<<._count_manually_removedLs)344 " "!!! " " " " " " " " " " " " " " "$&&}8@@GGHH s :>>r)balancedstrictmonitorzimunify-securityz rules.phpr{zOCould not load AI bot protection config for uid %s, counting it as disabled: %sFr)ai_bot_protectionpresetrr> imunify-coreimunify-antivirusimunify-wp-securityimunify360-firewallrrrr) waf_enabledrrr)waf_enabled_sitesai_bot_protection_enabled_sites!ai_bot_protection_preset_balancedai_bot_protection_preset_strict ai_bot_protection_preset_monitor) core_version av_versionfirewall_version wp_versioninstalled_sitesmanually_removed_sites server_configstatsiaid) r rDr@run_in_executorr%wp_cliget_content_dirr|uidpwdgetpwuidrpw_name Exceptionr5rtrdocrootboolrBrr WpSecurityPluginStatslenstrrrFrKrget_iaidrAprocess_message)r[sitesrmanually_removedrai_bot_enabled_sites preset_countsuser_ai_configsite content_dirdata_dir rules_php pw_recordr hoster_cfg ai_enabledrpkgsrrrrmsgs r8rjz ImunifySecurityPlugin.send_stats>s0  Fj007JKKKKKKKK   "&!;!; )" "        %&!BB *,, /, /D & 6t < <<<<<<>CcbKt}|stddS|j||jd{V}|r&t d|D|jd{V|j|dd{VtddS)Nz0No WordPress sites found for periodic processingcg|] }|j Sra)domain).0ss r8 zBImunifySecurityPlugin._process_installed_sites..s:::a:::r:)domainsrpT)delete_after_processing)days) r%r5r6rWprocess_changelogs_for_sitesrAr+rUcollect_incidents_for_sitesr,)r[raffected_sitess r8rz.ImunifySecurityPlugin._process_installed_sitess(#%%  LLK L L L F*GGtz         0::>:::Z        %AA $(B         'B//////r:cKjfd}|dd{VdS)z&Install plugin on new WordPress sites.cKtjjd{V}|rj||Sr)rrvrArTupdate)rr[s r8install_and_trackzFImunifySecurityPlugin._install_on_new_sites..install_and_tracksT$*$=4:$N$N$NNNNNNNO E,33ODDD" "r:T)rN)rTclearru)r[rs` r8_install_on_new_sitesz+ImunifySecurityPlugin._install_on_new_sitess $**,,, # # # # # ''    (           r:c\Ktj|j|jd{Vtj|jd{Vt jsW|tj|jd{Vd|_ d|_ | dSdS)zCTidy up sites from which the WordPress plugin was deleted manually.)rprTNrsF) rtidy_up_manually_deletedrArT$fix_data_file_permissions_everywherer rDrremove_all_installedrCrErrys r8_tidy_upzImunifySecurityPlugin._tidy_ups-$($@          9tzJJJJJJJJJJ0 ,''+<<<       +0D '%*D "  ) ) + + + + +  , ,r:cKtj|jd{V}|r|j|dSdS)zFAdopt sites where plugin is installed but not tracked in our database.rsN)radopt_found_sitesrArTr)r[ adopted_sitess r8_adopt_found_sitesz(ImunifySecurityPlugin._adopt_found_sitess^$6DJGGGGGGGGG   ?  ( / / > > > > > ? ?r:cJKtj|jd{VdS)z1Update plugin on all sites where it is installed.rsN)rupdate_everywhererArys r8_update_existingz&ImunifySecurityPlugin._update_existings4&DJ777777777777r:cK|d{V|jr |jd{V|d{V|d{V|d{VdS)z Combined operation: install on new sites, adopt found sites, tidy up, and update existing plugins. This runs all operations sequentially to avoid race conditions. N)rrPrrr rys r8_run_install_and_updatez-ImunifySecurityPlugin._run_install_and_update"s((*********  ! )( ( ( ( ( ( ( (%%'''''''''mmoo##%%%%%%%%%%%r:cRKtd|j|j|dr"td|jdS|jdkr%|jsdS|d{VdS|dr"td|jdS|dr"td|jdS|jd kr|d{VdS|jd kr| d{VdS|jd krP|jstd dStj | |_ dSdS) NzInstallation is not completed yet, skipping install_and_update)r5rtactionmethodrrrCrr rrrfr rR)r[messages r8manage_plugin_actionz*ImunifySecurityPlugin.manage_plugin_action5s J N N     ! !"; < <  NNI    F >3 3 3. ,,.. . . . . . . . F  ! !"5 6 6  NNC    F  ! !/ 2 2  NNG    F >. . .'')) ) ) ) ) ) ) ) F >Y & &--// ! ! ! ! ! ! ! F >1 1 1. * ,3+>,,..,,D ( ( ( 2 1r:cKt|dtsdStj}||jkrdS||_|r|js|dpidi}d|vrX tdddiid|_nB#t$rt dYnwxYwtj |_d|vrp tdddiid|_tj|_nZ#t$rt d Yn4wxYwtj|_tj|_|tj|j d{V|j!|j|jdSdS|sl|js|d rR|tj|j d{Vd|_|dSdSdS) Nconf submitted WORDPRESSrTz9waf_enabled config reset skipped, field not in schema yetrFz?ai_bot_protection config reset skipped, field not in schema yetrsrP) isinstancerr rDrErCrBdict_to_configrGrr5r6rrFrLrMrNrKrurvrArPrwrxrrrr)r[rcurrent_config_value submitted_wps r8manage_plugin_installationz0ImunifySecurityPlugin.manage_plugin_installationss"'&/<88  F(@ 4#9 9 9 F"6 @ ,(C@ ,$KK 44:??RLL00  NN11$}d&;<.2D**,LL3*0)G)I)I&",66  NN11$':E&BC49D0CEE77-LL38::,?AA3++)tz:::       %1&88021 & ,  ' ,%%&9:: ,''+<<<       +0D '  ) ) + + + + + , , , ,s%;,B(($CC/AD44$EEcKt|dtsdStjsdS|jsdSt j}t j}||jkr ||j krdS|j dtd{V}|s||_||_ dSt j |d{V}|t|kr||_||_ dSdS)a Propagate admin toggles of WORDPRESS.ai_bot_protection and WORDPRESS.ai_bot_protection_preset to every managed WP install's plugin_config.php immediately, so the WP plugin picks up the change at the next request rather than waiting for a scan cycle. Phase 2 per-account support extends *this* handler with a UserConfig branch (mirroring manage_waf_config); do not add a sibling handler. rN)rrr rDrCrrKrMrLrNr@rr%update_plugin_config_on_sitesr)r[rcurrent_enabledcurrent_presetrwrittens r8manage_ai_bot_protection_configz5ImunifySecurityPlugin.manage_ai_bot_protection_configs'&/<88  F0  F*  F >@@DFF t; ; ;$"EEE Fj007JKKKKKKKK +:D (2@D / FK|d{VdS)zPoll for license-edition changes and reconverge plugin_config.php. Edition changes reach only the external hook framework, never the message bus, so a poll is the propagation trigger. N)_reconverge_license_type_oncerys r8rkz-ImunifySecurityPlugin.reconverge_license_types20022222222222r:cFKtjsdS|jsdStj}||jkrdS|jdtd{V}|s ||_dStj |d{V}|t|kr ||_dSdSr`) r rDrCr get_license_typerOr@rr%rrr)r[currentrr"s r8r%z3ImunifySecurityPlugin._reconverge_license_type_onces0  F*  F-// d- - - Fj007JKKKKKKKK &-D # F no sites found for cleaned hitsz1Cleanup finished => %s site(s) need to be updatedc8g|]\}}t|d|S)r)rrr)r!)r site_pathrs r8rzIImunifySecurityPlugin.handle_malware_cleanup_finished..us;    3 9RS 9 9 9   r:z"%s site(s) updated after a cleanup)rErBr9rS resource_typergetpwnamuserr$pw_uid orig_file startswithaddr3r5r6rtrrupdate_data_on_sitesrA) r[rhits site_pathshit user_info user_sitesrrBwordpress_sitess r8handle_malware_cleanup_finishedz5ImunifySecurityPlugin.handle_malware_cleanup_finished=s %  F ;;x D ( ( I0F0F ( F));<<UU   C F**  # SX 6 6I!3I!>!>J,5? ((4&0"" =33I>>"&NNIs+;<<<!E" D+  LLN O O O F ?  OO     ",    )$*oFFFFFFFFF 8#o:N:NOOOOOs/A+C C('C(cK|jsdS|ddks*|dr|dsdS|d}t|}|std|dStdt |tj|j |d{Vtdt |dS) a INFO [2025-02-24 11:57:17,968] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'b9bd136aff0a4d87a248c859cfe41c47', 'scan_type': 'user', 'path': '/home/user1' } ) INFO [2025-02-24 12:00:10,740] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'a74271d2cdd04e0c9bd49ef6de23e0d8', 'scan_type': 'user', 'path': '/home/user4', 'started': 1740398383, 'total_files': 39229, 'total_malicious': 3, 'error': None, 'status': 'ok', 'scan_params': {'intensity_cpu': 2, 'intensity_io': 2, 'intensity_ram': 2048, 'initiator': None, 'file_patterns': None, 'exclude_patterns': None, 'follow_symlinks': False, 'detect_elf': True}, 'stats': {'scan_time': 27, 'mem_peak': 28217344, 'smart_time_hs': 0.004, 'scan_time_hs': 1.1751, 'smart_time_preg': 0, 'scan_time_preg': 2.7391, 'finder_time': 13.5896, 'cas_time': 0.7562, 'deobfuscate_time': 0.8998, 'total_files': 39229} } ) Nr=r>pathrz+Scan finished => no sites found for path=%sz.Scan finished => %s site(s) need to be updatedz%s site(s) updated after a scan) rErBr#r5r6rtrrrJrA)r[rrSrs r8handle_malware_scan_finishedz2ImunifySecurityPlugin.handle_malware_scan_finished~s8%  F KK ! !T ) );;v&& *;;w'' * Fv!$''  LLF M M M F  P>P.->P@ VI -..4C4C/.4C4C4Cr:r<)Wrloggingr contextlibrpathlibrtypingr defence360agent.contracts.configrrrr r %defence360agent.contracts.hook_eventsr !defence360agent.contracts.licenser "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsrrrdefence360agent.subsys.panelsr'defence360agent.subsys.persistent_staterrrdefence360agent.utilsrrrr defence360agent.utils.check_lockrdefence360agent.internals.iaidrdefence360agent.utils.commonrdefence360agent.wordpressrrrdefence360agent.wordpress.utilsr(defence360agent.wordpress.bot_protectionrdefence360agent.modelr defence360agent.model.wordpressr!r")defence360agent.wordpress.site_repositoryr#r$r%$defence360agent.wordpress.proxy_authr&r'r(r)r* defence360agent.wordpress.pluginr+(defence360agent.model.wordpress_incidentr, getLoggerrUr5rXr[r]r\r` CONFIG_DIRrrrmrBr4floatlistr9r<rar:r8rsE <;;;;;888888:::::: 877777  877777@@@@@@,,,,,,333333,,,,,,DDDDDD,+++++AAAAAAAA  LKKKKK  8 $ $  }en = = ..u~"4!3u~"" 21U^  TA B B  DL')KK"CChl "t 8 84 8 8 8 8F CF CF CF CF CKF CF CF CF CF Cr:defence360agent/plugins/__pycache__/wordpress.cpython-311.pyc0000644000000000000000000012244600000000000021202 0ustar r_jddlZddlZddlZddlmZddlmZddlmZddl m Z m Z m Z m Z mZddlmZddlmZddlmZdd lmZmZmZdd lmZdd lmZmZmZdd lm Z m!Z!m"Z"m#Z#dd l$m%Z%ddl&m'Z'ddl(m)Z)ddl*m+Z,ddl*m-Z-ddl.m/Z/ddl0m1Z1ddl2m3Z3ddl4m5Z5m6Z6ddl7m8Z8m9Z9m:Z:ddl;mZ>m?Z?m@Z@ddlAmBZBddlCmDZDejEeFZGede jHZIede jHZJede jHZKede jHZLedZMed ZNeMd!z ZOeMd"z ZPe!jQd#d$d%ZRd&eSd'eTfd(ZUGd)d*eeZVdS)+N)suppress)Path) Coroutine)ANTIVIRUS_MODEConfigValidationError SystemConfig UserConfig Wordpress) HookEvent) LicenseCLN) MessageType) MessageSink MessageSourceexpect) hosting_panel) load_stateregister_lock_file save_state)Scopeimporterrecurring_checksystem_packages_info) check_lock)IndependentAgentIDAPI)DAY)cli)plugin)_prepare_ai_bot_settings)resolve_ai_bot_protection) tls_check)WPSite WordpressSite)get_sites_by_pathget_sites_for_userget_installed_sites)is_secret_expired rotate_secret)ChangelogProcessorIncidentCollectorIncidentSender)update_disabled_rules_on_sites)delete_old_wordpress_incidentsz wp-gen-authzwp-site-processzwp-plugin-statszwp-license-reconvergez-/etc/sysconfig/imunify360/imunify360.config.dzF/opt/imunify360/venv/share/imunify360/11_on_first_install_wp_av.configz 11_on_first_install_wp_av.configz.11_on_first_install_wp_av.flagzimav.malwarelib.model MalwareHit)modulenamedefaultstarted_timestampreturnc|ttdgSt|S)z Get malware hits cleaned since the given timestamp with lazy import fallback. Returns empty list if imav.malwarelib is not available. Nz;imav.malwarelib not available, returning empty cleaned hits) _MalwareHitloggerdebug cleaned_since)r1s V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/plugins/wordpress.py_get_cleaned_malware_hitsr9_s?  I     $ $%6 7 77ceZdZejZdZdZdZdZ dZ dZ dZ dZ d ejfd Zd%d efd ZdZeedeedZeeddeedZeeddedZdZdZdZdZ dZ!dZ"e#e$j%dZ&e#e$j'dZ(e#e$j'dZ)eedde*dZ+d Z,e#e$j'd!Z-e#e.j/d"Z0e#e.j1d#Z2d$S)&ImunifySecurityPlugincd|_d|_td}|d|_|d}||n t j|_tj |_ tj |_ i|_ tj|_tj|_d|_d|_d|_d|_t-|_t1|_t5|_t9|_d|_d|_d|_ dS)Nr< installedenabled)!_loop_sinkrgetinstallation_completedr SECURITY_PLUGIN_ENABLEDlast_config_valuer_get_global_waf_enabled_last_waf_enabled_get_waf_default_last_waf_default_last_user_waf_enabled_get_global_ai_bot_protection_last_ai_bot_protection$_get_global_ai_bot_protection_preset_last_ai_bot_protection_preset_last_license_typeinstallation_task deleting_taskinstall_and_update_tasksetfreshly_installed_sitesr)incident_collectorr*incident_senderr(changelog_processor_site_processing_task _stats_task_license_reconverge_task)selfstatepersisted_enableds r8__init__zImunifySecurityPlugin.__init__ps/  233&+ii &<&<#"IIi00!,  2  "(!?!A!A!'!8!:!:>@#(.'K'M'M$  7 9 9 + #'6:26<@$47EE$#4"5"5-//#5#7#7 :>"04=A%%%r:c KdSN)r[loops r8 create_sinkz!ImunifySecurityPlugin.create_sinks  r:c\K||_||_|j||_|j||_|j||_|j| |_ tr| d{Vntd|d{VdS)NT) missing_ok)r@rA create_taskrefresh_auth_files_update_auth_taskprocess_wordpress_sitesrX send_statsrYreconverge_license_typerZr_apply_first_install_configFIRST_INSTALL_FLAGunlink _recover_installation_on_startup)r[rbsinks r8 create_sourcez#ImunifySecurityPlugin.create_sources'  !%!7!7  # # % %" " &*Z%;%;  ( ( * *& & " :11$//2C2CDD(, (>(>  ( ( * *) ) %  72244 4 4 4 4 4 4 4 4  % % % 6 6 63355555555555r:c,K|js tjsdStdd|_|tj|j d{V|j !|j |j dSdS)a Self-heal when the installation state was lost. If the feature is enabled but installation_completed is falsy (state file missing, earlier install interrupted, etc.), manage_plugin_installation can never recover: its True == True guard always returns early. Trigger install_everywhere once per restart to repopulate the wordpress_site table and flip the flag. NzXInstallation state is missing while feature is enabled; triggering startup self-recoveryTrp) rCr rDr5inforEprocess_installationrinstall_everywhererArPadd_done_callback_mark_installation_doner[s r8roz6ImunifySecurityPlugin._recover_installation_on_startups  ' 4  F  /   "&''  %4: 6 6 6           ! -  " 4 4,      . -r:cfKtsdStjd{VdkrKt t}t dt dS)Ni) rmexistsr HostingPanel users_countFIRST_INSTALL_CONFIG_PATH write_textFIRST_INSTALL_CONFIG_FILE read_textchmodrn)r[_s r8rlz1ImunifySecurityPlugin._apply_first_install_configs!((**  F+--99;; ; ; ; ; ; ;q @ @)44)3355A & + +E 2 2 2!!#####r:chK|j|jd{V|jr&|j|jd{V|jr&|j|jd{V|jr(|j|jd{VdSdSr`)rhcancelrXrYrZrys r8shutdownzImunifySecurityPlugin.shutdowns %%'''$$$$$$$$  % -  & - - / / /, , , , , , , ,   #   # # % % %" " " " " " " "  ( 0  ) 0 0 2 2 2/ / / / / / / / / / 0 0r:ct||std|dSt||}|duo)| o| S)NzUnknown task '%s'F)hasattrr5errorgetattrdone cancelled)r[task_attr_nametasks r8_task_in_progressz'ImunifySecurityPlugin._task_in_progresssit^,,  LL,n = = =5t^,,4L OLDNNr?)rrCrErys r8_save_installation_statez.ImunifySecurityPlugin._save_installation_states7 #!81       r:rcH|rtddS|}|td|dS|jstddSd|_|dS)NzInstallation task was cancelledzInstallation task failed: %sz>Feature was disabled during installation, skipping flag updateT)rr5rt exceptionrrErCr)r[rexcs r8rxz-ImunifySecurityPlugin._mark_installation_dones >>    KK9 : : : Fnn ? LL7 = = = F%  KK'    F&*# %%'''''r:FcorocK|dr\|r|dS|jr=|j |jd{Vn#tj$rYnwxYw|dr0t d|dSt j||_ dS)NrQrPzInstallation is already running) rcloserQrasyncioCancelledErrorr5warningrfrP)r[r for_new_sitess r8ruz*ImunifySecurityPlugin.process_installations  ! !/ 2 2   ! "))+++,,,,,,,,,-D  ! !"5 6 6  NN< = = = JJLLL F!(!4T!:!:s AA10A1cPK|drD|jr=|j |jd{Vn#tj$rYnwxYw|drt ddStj||_dS)NrPrQzDeleting is already running) rrPrrrr5rrfrQ)r[rs r8process_deletingz&ImunifySecurityPlugin.process_deleting$s  ! !"5 6 6 % &--///000000000-D  ! !/ 2 2  NN8 9 9 9 F$066s AAAT)check_period_firstcheck_lock_period lock_filecKtrtd{Vtj|jd{VdSNrs)r&r'rupdate_auth_everywhererArys r8rgz(ImunifySecurityPlugin.refresh_auth_files3s`    "// ! ! ! ! ! ! !+<<<<<<<<<<<._count_manually_removedLs)344 " "!!! " " " " " " " " " " " " " " "$&&}8@@GGHH s :>>r)balancedstrictmonitorzimunify-securityz rules.phpr{zOCould not load AI bot protection config for uid %s, counting it as disabled: %sFr)ai_bot_protectionpresetrr> imunify-coreimunify-antivirusimunify-wp-securityimunify360-firewallrrrr) waf_enabledrrr)waf_enabled_sitesai_bot_protection_enabled_sites!ai_bot_protection_preset_balancedai_bot_protection_preset_strict ai_bot_protection_preset_monitor) core_version av_versionfirewall_version wp_versioninstalled_sitesmanually_removed_sites server_configstatsiaid) r rDr@run_in_executorr%wp_cliget_content_dirr|uidpwdgetpwuidrpw_name Exceptionr5rtrdocrootboolrBrr WpSecurityPluginStatslenstrrrFrKrget_iaidrAprocess_message)r[sitesrmanually_removedrai_bot_enabled_sites preset_countsuser_ai_configsite content_dirdata_dir rules_php pw_recordr hoster_cfg ai_enabledrpkgsrrrrmsgs r8rjz ImunifySecurityPlugin.send_stats>s0  Fj007JKKKKKKKK   "&!;!; )" "        %&!BB *,, /, /D & 6t < <<<<<<>CcbKt}|stddS|j||jd{V}|r&t d|D|jd{V|j|dd{VtddS)Nz0No WordPress sites found for periodic processingcg|] }|j Sra)domain).0ss r8 zBImunifySecurityPlugin._process_installed_sites..s:::a:::r:)domainsrpT)delete_after_processing)days) r%r5r6rWprocess_changelogs_for_sitesrAr+rUcollect_incidents_for_sitesr,)r[raffected_sitess r8rz.ImunifySecurityPlugin._process_installed_sitess(#%%  LLK L L L F*GGtz         0::>:::Z        %AA $(B         'B//////r:cKjfd}|dd{VdS)z&Install plugin on new WordPress sites.cKtjjd{V}|rj||Sr)rrvrArTupdate)rr[s r8install_and_trackzFImunifySecurityPlugin._install_on_new_sites..install_and_tracksT$*$=4:$N$N$NNNNNNNO E,33ODDD" "r:T)rN)rTclearru)r[rs` r8_install_on_new_sitesz+ImunifySecurityPlugin._install_on_new_sitess $**,,, # # # # # ''    (           r:c\Ktj|j|jd{Vtj|jd{Vt jsW|tj|jd{Vd|_ d|_ | dSdS)zCTidy up sites from which the WordPress plugin was deleted manually.)rprTNrsF) rtidy_up_manually_deletedrArT$fix_data_file_permissions_everywherer rDrremove_all_installedrCrErrys r8_tidy_upzImunifySecurityPlugin._tidy_ups-$($@          9tzJJJJJJJJJJ0 ,''+<<<       +0D '%*D "  ) ) + + + + +  , ,r:cKtj|jd{V}|r|j|dSdS)zFAdopt sites where plugin is installed but not tracked in our database.rsN)radopt_found_sitesrArTr)r[ adopted_sitess r8_adopt_found_sitesz(ImunifySecurityPlugin._adopt_found_sitess^$6DJGGGGGGGGG   ?  ( / / > > > > > ? ?r:cJKtj|jd{VdS)z1Update plugin on all sites where it is installed.rsN)rupdate_everywhererArys r8_update_existingz&ImunifySecurityPlugin._update_existings4&DJ777777777777r:cK|d{V|jr |jd{V|d{V|d{V|d{VdS)z Combined operation: install on new sites, adopt found sites, tidy up, and update existing plugins. This runs all operations sequentially to avoid race conditions. N)rrPrrr rys r8_run_install_and_updatez-ImunifySecurityPlugin._run_install_and_update"s((*********  ! )( ( ( ( ( ( ( (%%'''''''''mmoo##%%%%%%%%%%%r:cRKtd|j|j|dr"td|jdS|jdkr%|jsdS|d{VdS|dr"td|jdS|dr"td|jdS|jd kr|d{VdS|jd kr| d{VdS|jd krP|jstd dStj | |_ dSdS) NzInstallation is not completed yet, skipping install_and_update)r5rtactionmethodrrrCrr rrrfr rR)r[messages r8manage_plugin_actionz*ImunifySecurityPlugin.manage_plugin_action5s J N N     ! !"; < <  NNI    F >3 3 3. ,,.. . . . . . . . F  ! !"5 6 6  NNC    F  ! !/ 2 2  NNG    F >. . .'')) ) ) ) ) ) ) ) F >Y & &--// ! ! ! ! ! ! ! F >1 1 1. * ,3+>,,..,,D ( ( ( 2 1r:cKt|dtsdStj}||jkrdS||_|r|js|dpidi}d|vrX tdddiid|_nB#t$rt dYnwxYwtj |_d|vrp tdddiid|_tj|_nZ#t$rt d Yn4wxYwtj|_tj|_|tj|j d{V|j!|j|jdSdS|sl|js|d rR|tj|j d{Vd|_|dSdSdS) Nconf submitted WORDPRESSrTz9waf_enabled config reset skipped, field not in schema yetrFz?ai_bot_protection config reset skipped, field not in schema yetrsrP) isinstancerr rDrErCrBdict_to_configrGrr5r6rrFrLrMrNrKrurvrArPrwrxrrrr)r[rcurrent_config_value submitted_wps r8manage_plugin_installationz0ImunifySecurityPlugin.manage_plugin_installationss"'&/<88  F(@ 4#9 9 9 F"6 @ ,(C@ ,$KK 44:??RLL00  NN11$}d&;<.2D**,LL3*0)G)I)I&",66  NN11$':E&BC49D0CEE77-LL38::,?AA3++)tz:::       %1&88021 & ,  ' ,%%&9:: ,''+<<<       +0D '  ) ) + + + + + , , , ,s%;,B(($CC/AD44$EEcKt|dtsdStjsdS|jsdSt j}t j}||jkr ||j krdS|j dtd{V}|s||_||_ dSt j |d{V}|t|kr||_||_ dSdS)a Propagate admin toggles of WORDPRESS.ai_bot_protection and WORDPRESS.ai_bot_protection_preset to every managed WP install's plugin_config.php immediately, so the WP plugin picks up the change at the next request rather than waiting for a scan cycle. Phase 2 per-account support extends *this* handler with a UserConfig branch (mirroring manage_waf_config); do not add a sibling handler. rN)rrr rDrCrrKrMrLrNr@rr%update_plugin_config_on_sitesr)r[rcurrent_enabledcurrent_presetrwrittens r8manage_ai_bot_protection_configz5ImunifySecurityPlugin.manage_ai_bot_protection_configs'&/<88  F0  F*  F >@@DFF t; ; ;$"EEE Fj007JKKKKKKKK +:D (2@D / FK|d{VdS)zPoll for license-edition changes and reconverge plugin_config.php. Edition changes reach only the external hook framework, never the message bus, so a poll is the propagation trigger. N)_reconverge_license_type_oncerys r8rkz-ImunifySecurityPlugin.reconverge_license_types20022222222222r:cFKtjsdS|jsdStj}||jkrdS|jdtd{V}|s ||_dStj |d{V}|t|kr ||_dSdSr`) r rDrCr get_license_typerOr@rr%rrr)r[currentrr"s r8r%z3ImunifySecurityPlugin._reconverge_license_type_onces0  F*  F-// d- - - Fj007JKKKKKKKK &-D # F no sites found for cleaned hitsz1Cleanup finished => %s site(s) need to be updatedc8g|]\}}t|d|S)r)rrr)r!)r site_pathrs r8rzIImunifySecurityPlugin.handle_malware_cleanup_finished..us;    3 9RS 9 9 9   r:z"%s site(s) updated after a cleanup)rErBr9rS resource_typergetpwnamuserr$pw_uid orig_file startswithaddr3r5r6rtrrupdate_data_on_sitesrA) r[rhits site_pathshit user_info user_sitesrrBwordpress_sitess r8handle_malware_cleanup_finishedz5ImunifySecurityPlugin.handle_malware_cleanup_finished=s %  F ;;x D ( ( I0F0F ( F));<<UU   C F**  # SX 6 6I!3I!>!>J,5? ((4&0"" =33I>>"&NNIs+;<<<!E" D+  LLN O O O F ?  OO     ",    )$*oFFFFFFFFF 8#o:N:NOOOOOs/A+C C('C(cK|jsdS|ddks*|dr|dsdS|d}t|}|std|dStdt |tj|j |d{Vtdt |dS) a INFO [2025-02-24 11:57:17,968] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'b9bd136aff0a4d87a248c859cfe41c47', 'scan_type': 'user', 'path': '/home/user1' } ) INFO [2025-02-24 12:00:10,740] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'a74271d2cdd04e0c9bd49ef6de23e0d8', 'scan_type': 'user', 'path': '/home/user4', 'started': 1740398383, 'total_files': 39229, 'total_malicious': 3, 'error': None, 'status': 'ok', 'scan_params': {'intensity_cpu': 2, 'intensity_io': 2, 'intensity_ram': 2048, 'initiator': None, 'file_patterns': None, 'exclude_patterns': None, 'follow_symlinks': False, 'detect_elf': True}, 'stats': {'scan_time': 27, 'mem_peak': 28217344, 'smart_time_hs': 0.004, 'scan_time_hs': 1.1751, 'smart_time_preg': 0, 'scan_time_preg': 2.7391, 'finder_time': 13.5896, 'cas_time': 0.7562, 'deobfuscate_time': 0.8998, 'total_files': 39229} } ) Nr=r>pathrz+Scan finished => no sites found for path=%sz.Scan finished => %s site(s) need to be updatedz%s site(s) updated after a scan) rErBr#r5r6rtrrrJrA)r[rrSrs r8handle_malware_scan_finishedz2ImunifySecurityPlugin.handle_malware_scan_finished~s8%  F KK ! !T ) );;v&& *;;w'' * Fv!$''  LLF M M M F  P>P.->P@ VI -..4C4C/.4C4C4Cr:r<)Wrloggingr contextlibrpathlibrtypingr defence360agent.contracts.configrrrr r %defence360agent.contracts.hook_eventsr !defence360agent.contracts.licenser "defence360agent.contracts.messagesr !defence360agent.contracts.pluginsrrrdefence360agent.subsys.panelsr'defence360agent.subsys.persistent_staterrrdefence360agent.utilsrrrr defence360agent.utils.check_lockrdefence360agent.internals.iaidrdefence360agent.utils.commonrdefence360agent.wordpressrrrdefence360agent.wordpress.utilsr(defence360agent.wordpress.bot_protectionrdefence360agent.modelr defence360agent.model.wordpressr!r")defence360agent.wordpress.site_repositoryr#r$r%$defence360agent.wordpress.proxy_authr&r'r(r)r* defence360agent.wordpress.pluginr+(defence360agent.model.wordpress_incidentr, getLoggerrUr5rXr[r]r\r` CONFIG_DIRrrrmrBr4floatlistr9r<rar:r8rsE <;;;;;888888:::::: 877777  877777@@@@@@,,,,,,333333,,,,,,DDDDDD,+++++AAAAAAAA  LKKKKK  8 $ $  }en = = ..u~"4!3u~"" 21U^  TA B B  DL')KK"CChl "t 8 84 8 8 8 8F CF CF CF CF CKF CF CF CF CF Cr:defence360agent/plugins/accumulate.py0000644000000000000000000000701700000000000014731 0ustar import asyncio import collections import os from logging import getLogger from defence360agent.api import inactivity from defence360agent.contracts.messages import ( Accumulatable, MessageType, Splittable, ) from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.utils import recurring_check, safe_cancel_task logger = getLogger(__name__) class Accumulate(MessageSink, MessageSource): PROCESSING_ORDER = MessageSink.ProcessingOrder.POST_PROCESS_MESSAGE SHUTDOWN_PRIORITY = ( 200 # Shutdown after regular plugins (100), before SendToServer ) DEFAULT_AGGREGATE_TIMEOUT = int( os.environ.get("IMUNIFY360_AGGREGATE_MESSAGES_TIMEOUT", 60) ) SHUTDOWN_SEND_TIMEOUT = int( os.environ.get("IMUNIFY360_AGGREGATE_SHUTDOWN_SEND_TIMEOUT", 50) ) def __init__( self, period=DEFAULT_AGGREGATE_TIMEOUT, shutdown_timeout=SHUTDOWN_SEND_TIMEOUT, **kwargs, ): super().__init__(**kwargs) self._period = period self._shutdown_timeout = shutdown_timeout self._data = collections.defaultdict(list) async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = ( None if self._period == 0 else loop.create_task(recurring_check(self._period)(self._flush)()) ) async def create_sink(self, loop): self._loop = loop async def shutdown(self): try: await asyncio.wait_for(self.stop(), self._shutdown_timeout) except asyncio.TimeoutError: # Used logger.error to notify sentry logger.error( "Timeout (%ss) sending messages to server on shutdown.", self._shutdown_timeout, ) if self._task is not None: await safe_cancel_task(self._task) async def stop(self): logger.info("Accumulate.stop cancel _task") if self._task is not None: await safe_cancel_task(self._task) logger.info("Accumulate.stop wait lock") # send pending messages await self._flush() @expect(MessageType.Accumulatable) async def collect(self, message: Accumulatable): list_types = ( message.LIST_CLASS if isinstance(message.LIST_CLASS, tuple) else (message.LIST_CLASS,) ) if message.do_accumulate(): with inactivity.track.task("accumulate"): for list_type in list_types: self._data[list_type].append(message) async def _flush(self): copy_data = self._data self._data = collections.defaultdict(list) for list_type, messages in copy_data.items(): batched = ( list_type.batched(messages) if issubclass(list_type, Splittable) else (messages,) ) for batch in batched: logger.info( f"Prepare {list_type.__name__}() " "for further processing" ) try: # FIXME: remove this try..except block after # we have forbidden to create Accumulatable class # without LIST_CLASS. await self._sink.process_message(list_type(items=batch)) except TypeError: logger.error("%s, %s", list_type, batch) raise defence360agent/plugins/analyst_cleanup_update.py0000644000000000000000000001303600000000000017330 0ustar import logging import asyncio from datetime import datetime from collections import namedtuple from peewee import OperationalError from defence360agent.contracts.plugins import MessageSource from defence360agent.subsys.persistent_state import register_lock_file, Scope from defence360agent.model.analyst_cleanup import AnalystCleanupRequest from defence360agent.utils import recurring_check from defence360agent.utils.common import DAY from defence360agent.utils.check_lock import check_lock from defence360agent.api.server.analyst_cleanup import AnalystCleanupAPI from defence360agent.utils.sshutil import remove_pub_key from defence360agent.internals.iaid import IAIDTokenError logger = logging.getLogger(__name__) LOCK_FILE = register_lock_file("analyst-cleanup-update", Scope.IM360) UpdateStatusRow = namedtuple( "UpdateStatusRow", ["zendesk_id", "new_status", "updated_at"] ) class AnalystCleanupUpdate(MessageSource): async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = loop.create_task( recurring_check( check_lock, check_period_first=True, check_lock_period=DAY / 2, lock_file=LOCK_FILE, )(self._update_task)() ) async def shutdown(self): self._task.cancel() # CancelledError is handled by @recurring_check(): await self._task @staticmethod async def _process( old_request, new_tickets_map, semaphore ) -> UpdateStatusRow | None: async with semaphore: zendesk_id = old_request.zendesk_id # Skip if the ticket wasn't found in the Zendesk response if zendesk_id not in new_tickets_map: logger.warning( f"Ticket {zendesk_id} not found in Zendesk API response" ) return ticket = new_tickets_map[zendesk_id] ticket_status = ticket["status"] updated_at = datetime.fromisoformat( ticket["updated_at"].replace("Z", "+00:00") ) # Determine new local status based on Zendesk ticket status new_status = { "new": "pending", "solved": "completed", "closed": "completed", }.get(ticket_status, "in_progress") # Update local status if it has changed if new_status and new_status != old_request.status: logger.info( f"Updating ticket {zendesk_id} status from" f" '{old_request.status}' to '{new_status}'" ) # If transitioning to completed, remove the SSH key if new_status == "completed": logger.info( f"Removing SSH key for user '{old_request.username}'" ) await asyncio.to_thread( remove_pub_key, old_request.username ) return UpdateStatusRow(zendesk_id, new_status, updated_at) @staticmethod def _update_db_statuses(rows: [UpdateStatusRow | None]): for ticket in rows: if not ticket: continue AnalystCleanupRequest.update_status( ticket.zendesk_id, ticket.new_status, ticket.updated_at ) async def _update_task(self): """ Gets all active and recently closed requests (for case if reopened). And asks all the requests status from zendesk API. Updates the state of the tickets in the database if changed. If any completed tickets, removes public key from relevant user. """ try: current_requests = ( AnalystCleanupRequest.get_all_relevant_requests() ) # Skip if there are no requests to check if not current_requests: logger.info( "No relevant analyst cleanup requests found to update" ) return except OperationalError as e: if "no such table" in str(e): logger.info("Database hasn't been updated yet") else: logger.error( f"Can't get data from analyst cleanup table: {e}" ) return # Extract Zendesk IDs from the requests zendesk_ids = [request.zendesk_id for request in current_requests] try: # Get ticket status updates from Zendesk API new_tickets = await AnalystCleanupAPI.get_tickets(zendesk_ids) if not new_tickets: logger.warning( "Didn't get tickets info from imunifyAPI but expected" ) return # Map from zendesk_id to ticket for easier lookup new_tickets_map = { str(ticket["id"]): ticket for ticket in new_tickets } # Process each request semaphore = asyncio.Semaphore(5) tasks = [ self._process(old_request, new_tickets_map, semaphore) for old_request in current_requests ] results = await asyncio.gather(*tasks) # Update the ticket status in the database await asyncio.to_thread(self._update_db_statuses, results) except IAIDTokenError as e: logger.error(f"IAIDTokenError: {e}") except Exception as e: logger.error(f"Error updating analyst cleanup requests: {e}") defence360agent/plugins/backup_info_sender.py0000644000000000000000000000575400000000000016434 0ustar import asyncio import time from datetime import timedelta from logging import getLogger from typing import Union from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import MessageSource from defence360agent.subsys.backup_systems import ( get_current_backend, get_last_backup_timestamp, ) from defence360agent.subsys.persistent_state import load_state, save_state from defence360agent.utils import Scope, recurring_check, safe_cancel_task logger = getLogger(__name__) SEND_INTERVAL = int(timedelta(hours=24).total_seconds()) RECURRING_CHECK_INTERVAL = 5 class BackupInfoSender(MessageSource): """Send user backup statistics to CH periodically""" SCOPE = Scope.IM360 async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._send_event = asyncio.Event() self._last_send_timestamp = self.load_last_send_timestamp() self._check_task = self._loop.create_task( self._recurring_check_data_to_send() ) self._send_stat_task = self._loop.create_task( self._recurring_send_stat() ) async def shutdown(self): for task in [self._check_task, self._send_stat_task]: await safe_cancel_task(task) self.save_last_send_timestamp() @staticmethod def is_valid_timestamp(timestamp: Union[int, float]) -> bool: return isinstance(timestamp, (int, float)) and timestamp > 0 def save_last_send_timestamp(self, ts: Union[int, float] = None): timestamp = self._last_send_timestamp if ts is None else ts if not self.is_valid_timestamp(timestamp): logger.warning("Invalid timestamp: %s", timestamp) return save_state("BackupInfoSender", {"last_send_timestamp": timestamp}) def load_last_send_timestamp(self): timestamp = load_state("BackupInfoSender").get("last_send_timestamp") if not self.is_valid_timestamp(timestamp): logger.warning("Invalid timestamp loaded, resetting to 0") timestamp = 0 return timestamp @recurring_check(RECURRING_CHECK_INTERVAL) async def _recurring_check_data_to_send(self): if time.time() - self._last_send_timestamp >= SEND_INTERVAL: self._send_event.set() @recurring_check(0) async def _recurring_send_stat(self): await self._send_event.wait() try: await self._send_server_config() except Exception as e: logger.exception("Failed to collect backup info: %s", e) finally: # Ensure backup info is not sent too frequently, even after an error self._last_send_timestamp = time.time() self._send_event.clear() async def _send_server_config(self): confg_msg = MessageType.BackupInfo( backup_provider_type=get_current_backend(), last_backup_timestamp=await get_last_backup_timestamp(), ) await self._sink.process_message(confg_msg) defence360agent/plugins/cagefs.py0000644000000000000000000001243300000000000014034 0ustar """ Goal: Invoke /usr/sbin/cagefsctl --update-etc /usr/sbin/cagefsctl --force-update-etc asynchronously. As far production scale `cagefsctl --force-update-etc` tends last for too long, e.g. - # time cagefsctl --force-update-etc Updating users ... Updating user user523 ... Updating user user804 ... ... Updating user user269 ... Updating user user116 ... Updating user user121 ... Updating user user117 ... real 2m44.454s user 0m26.233s sys 0m19.972s """ import asyncio import logging import os import subprocess import time from typing import Optional from defence360agent.api import inactivity from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import MessageSink, expect from defence360agent.subsys.persistent_state import load_state, save_state from defence360agent.utils import timefun _CAGEFSCTL_TOOL = "/usr/sbin/cagefsctl" _WAIT_LOCK = "--wait-lock" logger = logging.getLogger(__name__) class CageFS(MessageSink): async def create_sink(self, loop: asyncio.AbstractEventLoop): self._loop = loop self._queue = asyncio.Queue() self._last_force_update_ts = load_state("CageFS").get( "last_force_update_ts", 0 ) self._consumer_task = self._loop.create_task(self._consumer()) async def shutdown(self): self._consumer_task.cancel() await self._consumer_task if self._queue.qsize(): logger.warning("%d item(s) were not consumed", self._queue.qsize()) save_state( "CageFS", {"last_force_update_ts": self._last_force_update_ts} ) @expect(MessageType.ConfigUpdate) async def put_to_queue(self, message): config = message["conf"] username = getattr(config, "username", None) # not all ConfigUpdate messages mean the merged config file changed on disk # --force-update-etc is expensive so we wanna make sure the SystemConfig # actually changed on disk # OR it is a UserConfig change, in which case we process anyways if username is not None or config.modified_since( self._last_force_update_ts ): self._queue.put_nowait(username) async def _consumer(self): """ :raise never: """ while True: try: commitconfig_username = await self._queue.get() # that check is here because CageFS may be installed # just after Imunify agent installation/startup if not os.path.exists(_CAGEFSCTL_TOOL): continue # purge queue and eliminate duplicates uniq = {commitconfig_username} try: while True: uniq.add(self._queue.get_nowait()) except asyncio.QueueEmpty: pass with inactivity.track.task("cagefs"): for username in uniq: await self._commitconfig(username) except asyncio.CancelledError: # We are done return except Exception: logger.exception("Something went wrong") # Never. Stop. continue @timefun(log=logger.info) async def _commitconfig(self, username: Optional[str]): """ :raise asyncio.CancelledError: :raise Exception: """ if username: cmd = [_CAGEFSCTL_TOOL, _WAIT_LOCK, "--update-etc", username] else: cmd = [_CAGEFSCTL_TOOL, _WAIT_LOCK, "--force-update-etc"] # a config written while cagefsctl runs must still re-trigger a commit started_at = time.time() try: proc = await asyncio.create_subprocess_exec( *cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, # must not survive on agent stop/restart because of # stdout, stderr pipes start_new_session=False, ) future1 = self._passthru_log(cmd, logging.DEBUG, proc.stdout) future2 = self._passthru_log(cmd, logging.WARN, proc.stderr) await asyncio.gather(future1, future2) out, err = await proc.communicate() rc = await proc.wait() except asyncio.CancelledError: logger.warning("%r is terminated by CancelledError", cmd) raise else: if rc is None: logger.error("logic error: process has not terminated yet") elif rc: logger.error( "%r failed with rc [%s], stdout=%s, stderr=%s", cmd, rc, out, err, ) else: logger.info("%r succeeded with rc [%s]", cmd, rc) if username is None: self._last_force_update_ts = started_at @staticmethod async def _passthru_log(cmd, loglevel, streamreader): while True: line = await streamreader.readline() if not line: # EOF break logger.log(loglevel, "%r: %r", cmd, line) defence360agent/plugins/checkpoint.py0000644000000000000000000000235100000000000014731 0ustar from defence360agent.contracts.plugins import MessageSink from defence360agent.model.instance import db from defence360agent.utils import recurring_check class Checkpoint(MessageSink): """ Checkpoint imunify360.db periodically to limit unexpected WAL file growing. """ ONE_DAY = 24 * 60 * 60 def __init__(self, *, checkpoint_period=ONE_DAY, db=db): self._checkpoint_period = checkpoint_period self._db = db self._task = None async def create_sink(self, loop): self._loop = loop self._task = self._loop.create_task( recurring_check(self._checkpoint_period)(self._checkpoint)() ) async def shutdown(self): task, self._task = self._task, None # avoid cancelling twice if task is None or task.cancelled(): return task.cancel() # CancelledError is handled by @recurring_check(): await task async def _checkpoint(self): # 1. may not shrink database wal file in case of this command will be # during external read process took place # 2. returning immediately without result if database # has concurrent transaction self._db.execute_sql("PRAGMA wal_checkpoint(TRUNCATE)") defence360agent/plugins/client.py0000644000000000000000000004612100000000000014063 0ustar import asyncio import concurrent.futures import contextlib import json import logging import os import time import uuid from typing import Generator from defence360agent.api.server import ( APIError, APIErrorTooManyRequests, APITokenError, send_message, ) from defence360agent.contracts import license from defence360agent.contracts.config import Core from defence360agent.contracts.messages import ( GeneralMetrics, Message, MessageList, MessageType, ) from defence360agent.contracts.plugins import MessageSink, expect from defence360agent.internals import delivery_ack, feature_flags from defence360agent.internals.feature_flags import ( MESSAGE_LOSS_OBSERVABILITY_FLAG, is_enabled, ) from defence360agent.internals.message_status_publisher import Gen, publisher from defence360agent.internals.persistent_message import ( PersistentMessagesQueue, ) from defence360agent.utils import ( log_future_errors, recurring_check, safe_cancel_task, Scope, ) from defence360agent.utils.json import ServerJSONEncoder logger = logging.getLogger(__name__) _reporter_gen_queued = Gen() _reporter_gen_sending = Gen() _reporter_gen_sent = Gen() class SendToServerClient: """Send messages to server. * process Reportable messages; * add them to a pending messages list; * send all pending messages to server when list is full (contains _PENDING_MESSAGES_LIMIT items or more) or when the oldest pending message has waited the max send delay (0 unless batching is enabled via the feature flag); * send all pending messages on plugin shutdown.""" _PENDING_MESSAGES_LIMIT = int( os.environ.get("IMUNIFYAV_MESSAGES_COUNT_TO_SEND", 20) ) _MAX_SEND_DELAY = 0.0 _BATCHING_FLAG = "message_send_batching" # paces retries of messages re-queued after failed sends _SEND_MESSAGE_RECURRING_TIME = 60 _METRICS_REPORT_INTERVAL = 60 * 5 # 50 second because it should be less than DefaultTimeoutStopSec _SHUTDOWN_SEND_TIMEOUT = 50 _METRICS_FLUSH_TIMEOUT = 5 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._unsent_metrics = {} async def create_sink(self, loop: asyncio.AbstractEventLoop): self._loop = loop self._pending = PersistentMessagesQueue() self._try_send = asyncio.Event() self._lock = asyncio.Lock() self._shutting_down = asyncio.Event() self._flush_deadline = None self._metrics_task = loop.create_task(self._report_metrics()) self._sender_task = loop.create_task(self._send()) self._invoke_send_message_task = loop.create_task( self._invoke_send_message() ) @recurring_check(_METRICS_REPORT_INTERVAL) async def _report_metrics(self): await self._emit_metrics() def _collect_metrics(self) -> dict: return { "agent.persistent_queue.evicted": self._pending.pop_evicted(), "agent.msg_status.dropped": publisher.pop_dropped(), "agent.send.method_missing_dropped": ( send_message.pop_method_missing_dropped() ), } def _collect_gauges(self) -> dict: return { "agent.persistent_queue.size": self._pending.qsize(), "agent.persistent_queue.storage_size": self._pending.storage_size, "agent.msg_status.queue_size": publisher.queue_depth(), } async def _emit_metrics(self): # Deliver outside the persistent send-queue: a loss report routed # through it could be evicted by the very loss it reports. On failed # delivery the deltas roll into the next interval's report instead. metrics = self._unsent_metrics self._unsent_metrics = {} collected = self._collect_metrics() # Flag off: drain and discard so the first report after enabling # reflects only post-enable activity, not a backlog. if not is_enabled(MESSAGE_LOSS_OBSERVABILITY_FLAG): return for name, value in collected.items(): if value: metrics[name] = metrics.get(name, 0) + value sent = False try: # Gauge sampling can hit the DB, so it stays inside the guarded # region: any failure past this point must restore the popped # deltas rather than count toward recurring_check's error limit. # Gauges are point-in-time samples: taken fresh each interval and # never carried over — a stale depth is worse than a missing one. payload = {**metrics, **self._collect_gauges()} message = GeneralMetrics( [ {"name": name, "value": value} for name, value in payload.items() ] ) message["timestamp"] = time.time() message["message_id"] = uuid.uuid4().hex # Serialize with _send_pending_messages: the NATS sink shares one # gateway connection between both paths, and reconnecting closes it # and consumes the reconnect slot, so an unlocked metrics report # can break a batch that is in flight. async with self._lock: sent = await self._send_metrics_direct(message) except asyncio.CancelledError: # CancelledError is not an Exception: without this branch a # cancellation landing mid-send would eat the popped deltas. self._unsent_metrics = metrics raise except Exception as exc: logger.warning("Failed to deliver loss metrics: %r", exc) if not sent: self._unsent_metrics = metrics async def _send_metrics_direct(self, message: Message) -> bool: with self._get_api() as api: await api.send_messages( [(time.time(), self._encode_data_to_put_in_queue(message))] ) return True async def shutdown(self) -> None: """ When shutdown begins it signals any in-flight HTTP sends to abort immediately (via _shutting_down event), then gives 50 seconds to finish the stop() sequence. If stop() isn't done in 50 seconds it force-cancels the sender task. Finally, any messages still in the buffer are flushed to persistent storage so nothing is lost. """ # Signal shutdown — aborts in-flight HTTP requests from the # _send task via the asyncio.wait race in _send_pending_messages. # This lets stop() acquire the lock quickly instead of waiting # for a slow HTTP response. The event is cleared in stop() # before the final _send_pending_messages() flush so that # remaining messages are actually delivered during shutdown. self._shutting_down.set() try: await asyncio.wait_for(self.stop(), self._SHUTDOWN_SEND_TIMEOUT) except asyncio.TimeoutError: # Used logger.error to notify sentry logger.error( "Timeout (%ds) sending messages to server on shutdown.", self._SHUTDOWN_SEND_TIMEOUT, ) if not self._sender_task.cancelled(): await safe_cancel_task(self._sender_task) if self._pending.buffer_size > 0: logger.warning( "Save %s messages to persistent storage", self._pending.buffer_size, ) self._pending.push_buffer_to_storage() logger.warning("Stored queue %r", self._pending.qsize()) async def stop(self): """ Stop sending. 1. wait for the lock being available i.e., while _sender_task finishes the current round of sending message (if it takes too long, then the timeout in shutdown() is triggered 2. once the sending round complete (we got the lock), cancel the next iteration of the _sender_task (it exits) 3. send _pending messages (again, if it takes too long, the timeout in shutdown() is triggered and the coroutine is cancelled That method makes sure that the coroutine that was started in it has ended. It excludes a situation when: -> The result of a coroutine that started BEFORE shutdown() is started. -> And the process of sending messages from _pending is interrupted because of it """ # The _lock allows you to be sure that the _send_pending_messages # coroutine is not running and _pending is not being used logger.info("SendToServer.stop cancel _invoke_send_message_task") await safe_cancel_task(self._invoke_send_message_task) if self._metrics_task is not None: await safe_cancel_task(self._metrics_task) logger.info("SendToServer.stop wait lock") async with self._lock: # Cancel _sender_task. The lock ensures that the coroutine # is not in its critical part logger.info("SendToServer.stop lock acquired, cancel _sender_task") await safe_cancel_task(self._sender_task) # Clear the shutdown signal so the final flush actually # delivers messages instead of re-queuing them. self._shutting_down.clear() # send messages that are in _pending at the time of agent shutdown await self._send_pending_messages() if self._metrics_task is not None: # Final metrics flush: after the real messages so it cannot eat # their shutdown budget, time-bounded for the same reason, and # only once the task is cancelled so it cannot race this emit. with contextlib.suppress(asyncio.TimeoutError): await asyncio.wait_for( self._emit_metrics(), self._METRICS_FLUSH_TIMEOUT ) @staticmethod def _set_api_attrs(api): api.set_product_name(license.LicenseCLN.get_product_name()) api.set_server_id(license.LicenseCLN.get_server_id()) api.set_license(license.LicenseCLN.get_token()) return api @contextlib.contextmanager def _get_api(self) -> Generator[send_message.SendMessageAPI, None, None]: base_url = os.environ.get("IMUNIFYAV_API_BASE") # we send messages sequentially, so max_workers=1 with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor: api = send_message.SendMessageAPI( Core.VERSION, base_url, executor=executor ) yield self._set_api_attrs(api) @expect(MessageType.Reportable) async def send_to_server(self, message: Message) -> None: # add message handling time if it does not exist, so that # the server does not depend on the time it was received if "timestamp" not in message: message["timestamp"] = time.time() if "message_id" not in message: message["message_id"] = uuid.uuid4().hex self._pending.put(self._encode_data_to_put_in_queue(message)) self._try_send.set() publisher.report(message, _reporter_gen_queued, stage="agent-queued") @recurring_check(_SEND_MESSAGE_RECURRING_TIME) async def _invoke_send_message(self): self._try_send.set() def _max_send_delay(self) -> float: if feature_flags.is_enabled(self._BATCHING_FLAG): for value in feature_flags.get_params(self._BATCHING_FLAG): try: return float(value) except ValueError: pass return self._MAX_SEND_DELAY @recurring_check(0) async def _send(self): if self._flush_deadline is None: await self._try_send.wait() else: timeout = max(0, self._flush_deadline - self._loop.time()) with contextlib.suppress(asyncio.TimeoutError): await asyncio.wait_for(self._try_send.wait(), timeout) self._try_send.clear() qsize = self._pending.qsize() if qsize == 0: self._flush_deadline = None return if self._flush_deadline is None: self._flush_deadline = self._loop.time() + self._max_send_delay() if ( qsize < self._PENDING_MESSAGES_LIMIT and self._loop.time() < self._flush_deadline ): return self._flush_deadline = None # The _lock protects critical part of _send method logger.info("SendToServer._send wait lock") need_to_cancel = None async with self._lock: logger.info("SendToServer._send lock acquired") try: await self._send_pending_messages() except asyncio.CancelledError as e: logger.info("SendToServer._send cancelled unlocking") need_to_cancel = e logger.info("SendToServer._send lock released") if need_to_cancel: raise need_to_cancel def _encode_data_to_put_in_queue(self, data: Message) -> bytes: msg = json.dumps(data, cls=ServerJSONEncoder) + "\n" return msg.encode() def _decode_message(self, message: bytes) -> Message: data = json.loads(message) if data.get("list"): msg = MessageList(data["list"]) msg.update({k: v for k, v in data.items() if k != "list"}) return msg return Message(data) def _persist_failed(self, message_id, timestamp, message): message["api_retries_count"] = message.get("api_retries_count", 0) + 1 encoded = self._encode_data_to_put_in_queue(message) if message_id is None: # never stored yet: make it durable now, not on the next flush self._pending.put(encoded, timestamp=timestamp) self._pending.push_buffer_to_storage() else: self._pending.update_message(message_id, encoded) async def _send_one_message(self, api, message): """Race the HTTP send against the shutdown signal. Returns True on success, raises on API error, or returns False if shutdown interrupted the send. """ send_task = asyncio.ensure_future(api.send_message(message)) # Consume errors of an abandoned send; the handled path warns. send_task.add_done_callback( lambda task: log_future_errors(task, logger.debug) ) shutdown_task = asyncio.ensure_future(self._shutting_down.wait()) try: done, pending_tasks = await asyncio.wait( {send_task, shutdown_task}, return_when=asyncio.FIRST_COMPLETED, ) except asyncio.CancelledError: send_task.cancel() shutdown_task.cancel() raise for task in pending_tasks: await safe_cancel_task(task) if send_task in done: # Prefer send completion when both tasks finish in one loop turn. send_task.result() return True # Shutdown won the race return False async def _try_send_one(self, api, message_id, timestamp, message_bytes): """Deliver one message and return (stop, failed); message_id is None for a fresh memory-only message, set for a stored row.""" if self._shutting_down.is_set(): logger.warning( "Shutdown signal received, keeping remaining messages" ) return True, False message = self._decode_message(message_bytes) msg_info = { "method": message.get("method"), "message_id": message.get("message_id"), } try: publisher.report( message, _reporter_gen_sending, stage="agent-sending" ) sent = await self._send_one_message(api, message) if not sent: logger.warning( "Shutdown signal received during send," " keeping remaining messages" ) return True, False # Dropped, not delivered; the agent-sending report above stays, # so the loss surfaces as a stage gap rather than a delivery. if msg_info["method"]: publisher.report( message, _reporter_gen_sent, stage="agent-sent" ) logger.info("message sent %s", msg_info) delivery_ack.registry.confirm(message.get("message_id")) if message_id is not None: self._pending.delete([message_id]) return False, False except (APIErrorTooManyRequests, APITokenError) as exc: logger.warning( "Failed to send message %s to server: %s", msg_info, exc ) self._persist_failed(message_id, timestamp, message) return True, True except APIError as exc: logger.warning( "Failed to send message %s to server: %s", msg_info, exc ) self._persist_failed(message_id, timestamp, message) return False, True async def _send_pending_messages(self) -> None: with self._get_api() as api: if api.server_id is None: return # stored backlog (older, not deleted) first, then fresh buffer batch = list(self._pending.peek_stored()) + [ (None, timestamp, message_bytes) for timestamp, message_bytes in self._pending.drain_buffer() ] logger.info("Sending %s messages", len(batch)) failure_count = 0 processed = 0 try: for message_id, timestamp, message_bytes in batch: stop, failed = await self._try_send_one( api, message_id, timestamp, message_bytes ) if stop and not failed: # shutdown aborted this message before any attempt; # leave it in the un-attempted tail so it is persisted break processed += 1 if failed: failure_count += 1 if stop: # server-level stop: the rest won't send either failure_count += len(batch) - processed break finally: # un-attempted fresh messages are memory-only; stored are not unattempted_fresh = [ (ts, mb) for mid, ts, mb in batch[processed:] if mid is None ] if unattempted_fresh: self._pending.put_many(unattempted_fresh) self._pending.push_buffer_to_storage() logger.info("Unsuccessful to send %s messages", failure_count) class SendToServer(SendToServerClient, MessageSink): SCOPE = Scope.AV SHUTDOWN_PRIORITY = 900 # Shutdown late, after Accumulate has flushed async def _send_metrics_direct(self, message: Message) -> bool: with self._get_api() as api: if api.server_id is None: return False await api.send_message(message) return True defence360agent/plugins/config_merger.py0000644000000000000000000000147400000000000015415 0ustar import logging from defence360agent.contracts.config import ConfigValidationError, Merger from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import MessageSink, expect logger = logging.getLogger(__name__) class ConfigMerger(MessageSink): PROCESSING_ORDER = MessageSink.ProcessingOrder.PRE_PROCESS_MESSAGE def __init__(self): self.loop = None async def create_sink(self, loop): self.loop = loop @expect(MessageType.ConfigUpdate) async def update_merged_config(self, message): try: Merger.update_merged_config() except ConfigValidationError as err: logger.error("Config is invalid. Will not update: %s", err) finally: if event := message.get("event"): event.set() defence360agent/plugins/config_watcher.py0000644000000000000000000000361600000000000015571 0ustar import time from defence360agent.contracts import config from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.utils import recurring_check, Scope POLLING_INTERVAL = config.int_from_envvar("READ_CONFIG_POLLING_INTERVAL", 30) class ConfigWatcher(MessageSink, MessageSource): """Send ConfigUpdate message on [root's] config update. The config update is detected by polling config file's modification time. """ SCOPE = Scope.AV def __init__(self): self._config = config.ConfigFile() self._last_notify_time = 0 self._sink = None self._task = None async def create_sink(self, loop): "plugins.MessageSink method" @expect(MessageType.ConfigUpdate) async def on_config_update_message(self, message): # update the time, to avoid sending duplicate ConfigUpdate # messages after the "config update" command self._last_notify_time = message["timestamp"] async def create_source(self, loop, sink): self._sink = sink self._task = loop.create_task(self._check_config()) async def shutdown(self): if self._task is not None: t, self._task = self._task, None t.cancel() await t self._sink = None @recurring_check(POLLING_INTERVAL) async def _check_config(self): if config.any_layer_modified_since(self._last_notify_time): # notify about the update message = MessageType.ConfigUpdate( conf=self._config, timestamp=time.time() ) await self._sink.process_message(message) # update the time here, in case ConfigUpdate might stuck # in the queue for longer than the polling interval self._last_notify_time = message["timestamp"] defence360agent/plugins/event_hook_executor.py0000644000000000000000000000141100000000000016655 0ustar from defence360agent.contracts.hook_events import HookEvent from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.hooks.execute import execute_hooks EVENTS = ( HookEvent.AgentStarted, HookEvent.AgentMisconfig, HookEvent.LicenseExpired, HookEvent.LicenseExpiring, HookEvent.LicenseRenewed, ) class EventHookExecutor(MessageSink, MessageSource): PROCESSING_ORDER = MessageSink.ProcessingOrder.EVENT_HOOK async def create_sink(self, loop): self._loop = loop async def create_source(self, loop, sink): self._loop = loop self._sink = sink @expect(*EVENTS) async def receive_event(self, event): self._loop.create_task(execute_hooks(event)) defence360agent/plugins/event_monitor.py0000644000000000000000000000635200000000000015477 0ustar import json from abc import ABC from logging import getLogger from pathlib import Path from typing import Dict, List, Optional from defence360agent.contracts.config import Core from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import MessageSource from defence360agent.feature_management.plugins.native import ( NativeFeatureManagementSettingsChange, ) from defence360agent.plugins.event_monitor_message_processor import ( EventProcessorBase, UserConfigProcessor, ) from defence360agent.utils import recurring_check, safe_cancel_task logger = getLogger(__name__) class EventMonitor(MessageSource, ABC): EVENT_DIR = Core.INBOX_HOOKS_DIR PATTERN = "*.*.*.*.json" def __init__(self): self._loop = None self._sink = None self._processors: List[EventProcessorBase] = [] self._processing_task = None async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._processors.append(NativeFeatureManagementSettingsChange(loop)) self._processors.append(UserConfigProcessor(loop)) self._processing_task = self._loop.create_task( self._check_inbox_folder_generate_events() ) async def shutdown(self): await safe_cancel_task(self._processing_task) @staticmethod def _rmfile(file: Path): # pragma: no cover try: file.unlink() except FileNotFoundError: pass # do nothing if we cannot remove it, just skip it except Exception as e: logger.warning("Couldn't remove file %s %s", file, e) @staticmethod def _from_json(file: Path) -> Dict: return json.loads(file.read_text()) def _event_to_message(self, file) -> Optional[MessageType.cPanelEvent]: try: username, hook, ts1, ts2, *_ = file.name.split(".") ts = float(ts1 + "." + ts2) except ValueError: logger.warning("hook-event-file detected with wrong name %s", file) return None try: return MessageType.cPanelEvent.from_hook_event( username=username, hook=hook, ts=ts, fields=self._from_json(file), ) except FileNotFoundError: # pragma: no cover # already deleted logger.warning("hook file disappeared %s", file) except json.JSONDecodeError: # wrong format or broken json logger.warning("hook file have broken json %s", file) return None @recurring_check(30) async def _check_inbox_folder_generate_events(self): for file in Path(self.EVENT_DIR).glob("*.*.*.json"): try: message = self._event_to_message(file) if message is not None: for processor in self._processors: if await processor.is_enabled(): processor.add_message(message) except Exception as exc: # pragma: no cover logger.error("Failed to process %s hook event", exc) finally: self._rmfile(file) for processor in self._processors: await processor.process_messages() defence360agent/plugins/event_monitor_message_processor.py0000644000000000000000000001555700000000000021311 0ustar import asyncio import logging import os from abc import ABC, abstractmethod from collections import defaultdict from heapq import heappop, heappush from typing import Dict from defence360agent.contracts.config import Core from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import BaseMessageProcessor, expect from defence360agent.utils import is_safe_subdir_name, rmtree logger = logging.getLogger() class EventProcessorBase(BaseMessageProcessor, ABC): def __init__(self, loop): # note: empty list is a heap (no need for heapify here) self._msg_buf = defaultdict(list) self._loop = loop def add_message(self, message): heappush( self._msg_buf[message["username"]], (message["timestamp"], message) ) async def process_messages(self): await asyncio.gather( *( self.process_user_messages(user_messages) for user_messages in self._msg_buf.values() ) ) @expect(MessageType.cPanelEvent) async def process_event(self, message): if not self._message_is_relatable(message): # pragma: no cover return if message.hook == "Modify": await self._process_modify(message) elif message.hook == "Create": await self._process_create(message) elif message.hook == "change_package": await self._process_change_package(message) elif message.hook == "Remove": await self._process_account_removed(message) async def process_user_messages(self, messages): for _ in range(len(messages)): await self.process_message(heappop(messages)[1]) @abstractmethod async def _process_modify(self, message): """Modify hook""" @abstractmethod async def _process_create(self, message): """Create hook""" @abstractmethod async def _process_change_package(self, message): """change_package hook""" @abstractmethod async def _process_account_removed(self, message): """Remove hook""" @abstractmethod def _message_is_relatable(self, message): """Whether the message should be processed""" @abstractmethod async def is_enabled(self): """Whether messages should be processed""" class SettingsChangeBase(EventProcessorBase, ABC): """Process hook event messages from cPanel""" async def _process_modify(self, message): package_field = "plan" if "plan" in message.data else "exclude" await self._get_settings_and_update(message, package_field) async def _process_create(self, message): await self._get_settings_and_update(message, "plan", True) async def _process_change_package(self, message): await self._get_settings_and_update(message, "new_pkg", True) async def _process_account_removed(self, message): pass async def _get_settings_and_update( self, message, package_field: str, add_to_package: bool = False, ) -> None: logger.info("Get settings from %s", message) settings = await self._get_settings_from_message(message) await self._apply_settings( message, package_field, add_to_package, settings ) async def _apply_settings( self, message, package_field, add_to_package, settings ): logger.info("Step 1 %s ", settings) # Do nothing if there are no values for Imunify360 features # in the message for Modify hook if ( message.get("plan") is None and message["hook"] == "Modify" and all(value is None for value in settings.values()) ): return if not all(settings.values()): try: package_name = message.data[package_field] except KeyError: logger.warning("No information about package in message") fallback_settings = self._default_settings() else: fallback_settings = await self._get_package_settings( package_name, add_to_package ) for feature, value in settings.items(): if value is None: settings[feature] = fallback_settings[feature] logger.info( "Settings specified in hook message %s for %s", settings, message["username"], ) for feature, value in settings.items(): await self.on_settings_change(message["username"], feature, value) @abstractmethod def _message_is_relatable(self, message): """Whether the message should be processed""" @abstractmethod async def on_settings_change(self, user, feature, value): """What to do after settings were changed (e.g. sync the DB)""" @staticmethod @abstractmethod def _default_settings() -> Dict[str, str]: """Get default package settings""" @abstractmethod async def _get_settings_from_message(self, message): """Retrieve settings from the message""" @classmethod @abstractmethod async def _get_package_settings( cls, package_name: str, add_to_package: bool ) -> Dict[str, str]: """Get current package settings""" @abstractmethod async def is_enabled(self): """Whether messages should be processed""" class UserConfigProcessor(EventProcessorBase): def _message_is_relatable(self, message): return True async def is_enabled(self): return True async def _process_account_removed(self, message): user = message.get("user") or message.get("username") if not is_safe_subdir_name(user): return target = os.path.join(Core.USER_CONFDIR, user) try: rmtree(target) except FileNotFoundError: pass except OSError as e: logger.warning( "Failed to remove user_config dir %s: %s", target, e ) async def _process_modify(self, message): old_username = message.data.get("old_username") new_username = message.username if not ( old_username and is_safe_subdir_name(old_username) and is_safe_subdir_name(new_username) ): return try: os.rename( os.path.join(Core.USER_CONFDIR, old_username), os.path.join(Core.USER_CONFDIR, new_username), ) except FileNotFoundError: pass except OSError as e: logger.warning( "Failed to rename user_config %s -> %s: %s", old_username, new_username, e, ) async def _process_create(self, message): """Create hook""" async def _process_change_package(self, message): """change_package hook""" defence360agent/plugins/feature_flags.py0000644000000000000000000002137000000000000015413 0ustar """ Feature flags synchronisation plugin (AV mode only). In IM360 mode the Go resident-agent handles feature-flag sync. In AV mode there is no resident-agent, so this plugin takes over. Periodically POSTs the local file checksum to the API and writes back any updated flags to ``/var/imunify360/feature_flags.json`` (legacy map ``{flag: true}`` on disk) and ``/var/imunify360/feature_flags`` (plain names, one per line). The POSTed checksum is over the canonical JSON **array** of enabled names, matching the correlation sync API—not over the on-disk map bytes. """ import asyncio import json import logging import os import urllib.error import urllib.request from defence360agent.contracts.config import Core from defence360agent.contracts.plugins import MessageSource from defence360agent.internals.feature_flags import ( FLAGS_PATH, FLAGS_PLAIN_PATH, enabled_flag_names_sorted, plain_text_payload_for_enabled_flags, serialize_feature_flags_file_payload, sync_checksum_hex_from_flags_file, sync_response_file_bytes, ) from defence360agent.internals.iaid import ( IAIDTokenError, IndependentAgentIDAPI, ) from defence360agent.utils import Scope, atomic_rewrite logger = logging.getLogger(__name__) _SYNC_URL = "/api/sync/v1/feature-flags" def _env_int(name: str, default: int) -> int: """Read an int env var tolerantly. A non-numeric value (empty string, typo, etc.) must NOT raise at import time — the plugin lives in the AV agent entry point and a bad env var would otherwise kill the whole agent. """ raw = os.environ.get(name) if not raw: return default try: return int(raw) except ValueError: logger.warning( "feature-flags: %s=%r is not an int, using default %d", name, raw, default, ) return default _TRUE_VALUES = frozenset({"1", "true", "yes", "on"}) _FALSE_VALUES = frozenset({"0", "false", "no", "off"}) def _env_bool(name: str, default: bool) -> bool: raw = os.environ.get(name) if not raw: return default normalized = raw.strip().lower() if normalized in _TRUE_VALUES: return True if normalized in _FALSE_VALUES: return False logger.warning( "feature-flags: %s=%r is not a bool, using default %s", name, raw, default, ) return default _SYNC_INTERVAL = _env_int("I360_FEATURE_FLAGS_SYNC_INTERVAL", 3600) _INITIAL_DELAY = _env_int("I360_FEATURE_FLAGS_INIT_DELAY", 10) _UNREGISTERED_DELAY = _env_int("I360_FEATURE_FLAGS_UNREG_DELAY", 30) _USE_SERVER_DELAY = _env_bool("I360_FEATURE_FLAGS_USE_SERVER_DELAY", True) _HTTP_TIMEOUT = 30 def _next_delay(server_delay: int) -> int: if _USE_SERVER_DELAY and server_delay > 0: return server_delay return _SYNC_INTERVAL class FeatureFlagsSync(MessageSource): SCOPE = Scope.AV async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = loop.create_task(self._sync_loop()) async def shutdown(self): if self._task is not None: self._task.cancel() try: await self._task except asyncio.CancelledError: pass def _local_checksum(self) -> str: return sync_checksum_hex_from_flags_file(FLAGS_PATH) async def _sync_loop(self): await asyncio.sleep(_INITIAL_DELAY) while True: delay = _SYNC_INTERVAL try: if not IndependentAgentIDAPI.is_registered(): delay = _UNREGISTERED_DELAY else: delay = _next_delay(await self._do_sync()) except asyncio.CancelledError: raise except Exception: logger.warning("feature flags sync failed", exc_info=True) await asyncio.sleep(delay) async def _do_sync(self) -> int: try: token = await IndependentAgentIDAPI.get_token() except IAIDTokenError: logger.warning("no IAID token, skipping feature flags sync") return 0 loop = asyncio.get_event_loop() checksum = await loop.run_in_executor(None, self._local_checksum) payload = json.dumps({"checksum": checksum}).encode() base_url = os.getenv("I360_FEATURE_FLAGS_API_URL", Core.API_BASE_URL) url = base_url.rstrip("/") + _SYNC_URL req = urllib.request.Request( url, data=payload, headers={ "Content-Type": "application/json", "X-Auth": token, }, method="POST", ) try: resp_body = await loop.run_in_executor( None, self._blocking_request, req ) except urllib.error.HTTPError as e: # Non-5xx (404/403/4xx) is usually a server-side routing or # auth state, not an agent bug — keep it a one-line WARNING. # 5xx means the server actually misbehaved; keep the traceback. if 500 <= e.code < 600: logger.error( "feature flags sync HTTP %s on %s: %s", e.code, url, e.reason, ) else: logger.warning( "feature flags sync HTTP %s on %s: %s", e.code, url, e.reason, ) return 0 except (urllib.error.URLError, TimeoutError) as e: # DNS, connection refused, TLS, timeout — transient network # conditions, not bugs. One-line WARNING so logs stay readable. # A timeout during resp.read() escapes urlopen as a bare # TimeoutError, not wrapped in URLError. logger.warning( "feature flags sync connection failed on %s: %s", url, getattr(e, "reason", e), ) return 0 except Exception: logger.error( "feature flags sync request failed on %s", url, exc_info=True, ) return 0 try: result = json.loads(resp_body) except json.JSONDecodeError: logger.error("failed to parse feature flags response") return 0 server_delay = result.get("delay", 0) if result.get("changed") is False: logger.debug("feature flags unchanged, skipping write") return server_delay flags = result.get("flags") params = result.get("params") or {} if flags is not None: await loop.run_in_executor(None, self._write_flags, flags, params) return server_delay @staticmethod def _blocking_request(req: urllib.request.Request) -> bytes: with urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT) as resp: return resp.read() @staticmethod def _write_flags(flags, params=None) -> None: """Persist flags + params on disk in the canonical sync-response shape so the next sync's checksum matches what the server returned. Falls back to the legacy ``{name: true}`` map when ``flags`` is not a list (response shape we don't recognise) — keeps the long-standing on-disk contract from older code paths. """ params = params or {} try: if isinstance(flags, list): names = [n for n in flags if isinstance(n, str)] cleaned = { name: [v for v in vals if isinstance(v, str)] for name, vals in params.items() if isinstance(name, str) and isinstance(vals, list) } data = sync_response_file_bytes(names, cleaned) else: data = serialize_feature_flags_file_payload(flags) except TypeError: logger.warning( "feature flags sync: unexpected flags type %r, skipping write", type(flags).__name__, ) return n_active = len(enabled_flag_names_sorted(flags)) try: os.makedirs(os.path.dirname(FLAGS_PATH), exist_ok=True) # Atomic write-to-temp + rename so a crash mid-write can't # leave the flags file truncated/corrupt — otherwise readers # would fall back to defaults until the next sync. atomic_rewrite(FLAGS_PATH, data, backup=False) plain = plain_text_payload_for_enabled_flags(flags) atomic_rewrite(FLAGS_PLAIN_PATH, plain, backup=False) logger.info("feature flags synced: %d flags active", n_active) except OSError: logger.error("failed to write flags file", exc_info=True) defence360agent/plugins/files_recurring_update.py0000644000000000000000000000214100000000000017323 0ustar import logging from defence360agent import files from defence360agent.contracts import config, messages from defence360agent.contracts.plugins import MessageSource from defence360agent.utils import recurring_check logger = logging.getLogger(__name__) class FilesRecurringUpdateTask(MessageSource): async def _on_files_update( self, index: files.Index, is_updated: bool ) -> None: if is_updated: message = messages.MessageType.FilesUpdated(index.type, index) await self._sink.process_message(message) async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = loop.create_task(self._update_task()) # subscribe to file updates for type_ in files.Index.types(): files.Index.add_hook(type_, self._on_files_update) async def shutdown(self): self._task.cancel() # CancelledError is handled by @recurring_check(): await self._task @recurring_check(config.FilesUpdate.PERIOD) async def _update_task(self): await files.update_and_log_error() defence360agent/plugins/icontact_sender.py0000644000000000000000000001066100000000000015751 0ustar import asyncio import logging import time from pathlib import Path from defence360agent.internals.iaid import IAIDTokenError from defence360agent.api.server import APIError from defence360agent.api.server.events import EventsAPI from defence360agent.contracts.config import ( Core, IContactMessageType, ) from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, ) from defence360agent.internals.the_sink import TheSink from defence360agent.model.icontact import IContactThrottle from defence360agent.subsys.panels.cpanel import cPanel from defence360agent.subsys.panels.plesk import Plesk from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.utils import ( await_for, create_task_and_log_exceptions, recurring_check, retry_on, Scope, ) from defence360agent.utils.common import DAY logger = logging.getLogger(__name__) async def async_log_on_error(e, i): logger.warning( "Can't get recommendations for the dashboard due to " "iaid token error, reason: %s. Attempt %s", e, i, ) await_for(seconds=100) class IContactSender(MessageSink, MessageSource): PROCESSING_ORDER = MessageSink.ProcessingOrder.ICONTACT_SENT SCOPE = Scope.AV_IM360 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._tasks = [] self._notification_flag_path = ( Path(Core.TMPDIR) / "icontact_generic_notifications" ) async def create_sink(self, loop): pass async def _send_icontact_message( self, *, message_type, params, period_limit, user=None, ): if message_type is None: return if not IContactThrottle.may_be_notified( message_type, period_limit, user=user, ): return template_args = await self._panel.notify( message_type=IContactMessageType.GENERIC, params=params, user=user, ) if template_args: IContactThrottle.refresh(message_type, user=user) sent_message = MessageType.IContactSent( message_type=message_type, timestamp=int(time.time()), template_args=template_args, ) await self._sink.process_message(sent_message) async def create_source(self, loop, sink: TheSink): self._sink = sink self._panel = HostingPanel() if self._panel.NAME in [cPanel.NAME, Plesk.NAME]: self._tasks = [ create_task_and_log_exceptions( loop, self.generic_notifications ) ] async def shutdown(self): for task in self._tasks: task.cancel() await asyncio.gather(*self._tasks, return_exceptions=True) @retry_on( APIError, on_error=await_for(seconds=10), max_tries=3, silent=True, log=logger, ) @retry_on( IAIDTokenError, on_error=async_log_on_error, max_tries=3, silent=True, log=logger, ) async def get_notifications(self) -> list: notifications = [] if ( not self._notification_flag_path.exists() or (self._notification_flag_path.stat().st_mtime + DAY) < time.time() ): # send notification request no more than once a day notifications = await EventsAPI.notification() # update flag modify time self._notification_flag_path.touch(mode=0o644, exist_ok=True) return notifications @recurring_check(DAY) async def generic_notifications(self): if notifications := await self.get_notifications(): logger.info( "Sending %s generic icontact notifications", len(notifications) ) for notification in notifications: await self._send_icontact_message( message_type=notification["type"], params={ "subject": notification["notification_subject"], "body_html": notification["notification_body_html"], }, period_limit=notification["notification_period_limit"], user=notification.get("notification_user"), ) defence360agent/plugins/idle_time_out.py0000644000000000000000000000232700000000000015427 0ustar from logging import getLogger from defence360agent.api import inactivity from defence360agent.contracts.config import SimpleRpc from defence360agent.contracts.plugins import MessageSink from defence360agent.utils import clip, fail_agent_service, recurring_check logger = getLogger(__name__) class IdleTimeOutCheck(MessageSink): async def create_sink(self, loop): self._loop = loop if SimpleRpc.SOCKET_ACTIVATION: inactivity.track.reset_timer() self._task = loop.create_task( recurring_check( period=clip( SimpleRpc.INACTIVITY_TIMEOUT // 5, low=1, high=60 ), )( self._check_timeout, )() ) else: self._task = None async def shutdown(self): if self._task: self._task.cancel() # CancelledError is handled by @recurring_check(): await self._task async def _check_timeout(self): logger.info("Periodical check %s ", inactivity.track) if inactivity.track.is_timeout(): logger.warning("Shutting down due to inactivity.") fail_agent_service() defence360agent/plugins/lve_utils_install.py0000644000000000000000000000343100000000000016336 0ustar from defence360agent.contracts.plugins import MessageSink from defence360agent.utils import ( check_run_outside_sandbox, recurring_check, RecurringCheckStop, ) from defence360agent.utils.resource_limits import is_lve_active, has_lvectl class LveUtilsAutoInstaller(MessageSink): """ Install lve-utils package on CL with LVE automatically (according to DEF-11452) to provide tools to limit CPU/IO. Used tools: /usr/sbin/lvectl - provided by lve-utils package /bin/lve_suwrapper - provided by lve-wrappers package (which is a dependency of lve-utils) lve-utils package is installed by default on CL, but for some reason may not exist. """ def __init__(self, *, check_period=3600): self._check_period = check_period self._task = None async def create_sink(self, loop): self._loop = loop self._task = self._loop.create_task( recurring_check(self._check_period)( self._install_lve_utils_if_needed )() ) async def shutdown(self): if self._task is not None: self._task.cancel() await self._task self._task = None async def _install_lve_utils_if_needed(self): if not is_lve_active(): # kernel doesn't support lve or it is disabled # no point trying to install lve-utils raise RecurringCheckStop() # suppose that lve should be actived on CL only if not has_lvectl(): # utilities might have been removed # DEF-41613: yum install triggers RPM scriptlets whose LSM # transition on exec is blocked by the agent unit's NNP. await check_run_outside_sandbox( ["yum", "-y", "install", "lve-utils"] ) defence360agent/plugins/myimunify.py0000644000000000000000000000375100000000000014635 0ustar import logging from defence360agent.contracts.config import MyImunifyConfig from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.myimunify.model import update_users_protection from defence360agent.subsys.panels import hosting_panel from defence360agent.subsys.persistent_state import load_state, save_state logger = logging.getLogger(__name__) class MyImunifyPlugin(MessageSink, MessageSource): def __init__(self): self._previous_myimunify_status = ( load_state("MyImunifyPlugin").get("myimunify_enabled") or MyImunifyConfig.ENABLED ) self._loop = None self._sink = None async def shutdown(self): save_state( "MyImunifyPlugin", {"myimunify_enabled": self._previous_myimunify_status}, ) async def create_sink(self, loop): pass async def create_source(self, loop, sink): self._loop = loop self._sink = sink async def _update_myimunify_users(self): existing_users = await hosting_panel.HostingPanel().get_users() await update_users_protection( self._sink, existing_users, False, force_config_update=True ) @expect(MessageType.ConfigUpdate) async def on_config_update(self, message: MessageType.ConfigUpdate): myimunify_enabled = MyImunifyConfig.ENABLED previous_status = self._previous_myimunify_status # We're also triggering additional MessageType.ConfigUpdate messages # so we must update previous_status before triggering new one self._previous_myimunify_status = myimunify_enabled if myimunify_enabled and not previous_status: await self._update_myimunify_users() if myimunify_enabled != previous_status: await hosting_panel.HostingPanel().switch_ui_config( myimunify_enabled=myimunify_enabled ) defence360agent/plugins/ping.py0000644000000000000000000000103000000000000013530 0ustar from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) class SendPing(MessageSource, MessageSink): async def create_sink(self, loop): self._loop = loop async def create_source(self, loop, sink): self._loop = loop self._sink = sink @expect(MessageType.ServerConnected, MessageType.ServerReconnected) async def send_ping(self, _): await self._sink.process_message(MessageType.Ping()) defence360agent/plugins/send_domain_list.py0000644000000000000000000000600500000000000016115 0ustar import datetime import logging import pwd import time from typing import AsyncIterator from defence360agent.contracts.config import ( int_from_envvar, ) from defence360agent.contracts.messages import DomainList from defence360agent.contracts.myimunify_id import get_myimunify_users from defence360agent.contracts.plugins import ( MessageSink, MessageSource, ) from defence360agent.subsys.panels.base import PanelException from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.utils import ( Scope, recurring_check, split_for_chunk, ) logger = logging.getLogger(__name__) class SendDomainList(MessageSink, MessageSource): SCOPE = Scope.AV_IM360 def __init__(self, period=None): self._task = None if period: self._period = period else: self._period = int_from_envvar( "IMUNIFY360_SEND_DOMAIN_PERIOD", int(datetime.timedelta(days=1).total_seconds()), ) async def create_sink(self, loop): """MessageSink method""" async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = self._loop.create_task( recurring_check(self._period)(self._send_domain_list)() ) async def shutdown(self): if self._task is not None: self._task, t = None, self._task t.cancel() await t def _panel_domain_type_to_imunify(self, panel_type: str) -> str: return { "main": "primary", "parked": "alias", }.get(panel_type, panel_type) async def _create_domain_list_msg(self) -> AsyncIterator[DomainList]: hp = HostingPanel() logger.info("HostingsPanel: %s", hp.NAME) domains = [] myimunify_users = await get_myimunify_users() for user in myimunify_users: username = user["username"] user_pwd = pwd.getpwnam(username) for domain_data in await hp.get_user_domains_details(username): domains.append( { "username": username, "docroot": domain_data.docroot, "name": domain_data.domain, "securesite_user_id": user["myimunify_id"], "uid": user_pwd.pw_uid, "type": self._panel_domain_type_to_imunify( domain_data.type ), } ) timestamp = time.time() for chunk in split_for_chunk(domains, chunk_size=3000): msg = DomainList() msg["timestamp"] = timestamp msg["domains"] = chunk yield msg async def _send_domain_list(self): try: async for msg in self._create_domain_list_msg(): await self._sink.process_message(msg) except PanelException as e: logger.warning("Domain list report skipped: %s", e) defence360agent/plugins/send_server_config.py0000644000000000000000000002636700000000000016463 0ustar import binascii import datetime import hashlib import os import re import uuid from functools import lru_cache from logging import getLogger from pathlib import Path from typing import Dict, List from defence360agent.contracts import sentry from defence360agent.contracts.config import ( ConfigFile, Core, CustomBillingConfig, Malware, MalwareSignatures, SystemConfig, int_from_envvar, FREEMIUM_FEATURE_FLAG, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.contracts.myimunify_id import get_myimunify_users from defence360agent.feature_management.control import ( is_native_feature_management_enabled, is_native_feature_management_supported, ) from defence360agent.internals.iaid import IndependentAgentIDAPI from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.subsys.panels.cpanel import cPanel from defence360agent.utils import ( log_error_and_ignore, recurring_check, safe_cancel_task, Scope, stub_unexpected_error, safe_run, system_packages_info, ) from defence360agent.subsys.persistent_state import load_state, save_state from defence360agent.utils.whmcs import WhmcsConf #: info about these paths is sent to server in SERVER_CONFIG CH_PATHS = ( "/var/imunify360/imunify360.db", "/var/imunify360/imunify360.db-shm", "/var/imunify360/imunify360.db-wal", "/var/imunify360/gw.dir/", ) logger = getLogger(__name__) # Components which version sends to CH PACKAGES_TO_REPORT = { "imunify360-firewall", "imunify-antivirus", "ai-bolit", "app-version-detector", "imunify360-php-i360", "imunify360-webshield-bundle", "imunify-realtime-av", "imunify-realtime-av-imrt2", "imunify-auditd-log-reader", "imunify360-pam", "imunify-notifier", "imunify360-unified-access-logger", "imunify360-ossec-server", "imunify360-ossec", "imunify-core", "imunify-ui", "imunify360-venv", "imunify-patchman", "imunify-wp-security", "rustbolit", "imunify-release", "imunify-common", "alt-common-release", "alt-php-hyperscan", "alt-php-internal", "cloudlinux-backup-utils", "minidaemon", } def read_cpu_info(): with open("/proc/cpuinfo") as f: return f.read() @lru_cache(maxsize=1) def get_cpu_info(): text = read_cpu_info() tuples = re.findall("^(.*?)[ \t]*:[ \t]*(.*)$", text, flags=re.M) res: List[dict] = [] current = {} for key, value in tuples: if key == "processor": if current and "processor" in current: res.append(current) current = {} current[key] = value res.append(current) return res @stub_unexpected_error def get_cpu_cores(): physical_ids = {} for processor in get_cpu_info(): if ( physical_id := processor.get("physical id", processor["processor"]) ) not in physical_ids: physical_ids[physical_id] = int(processor.get("cpu cores", 1)) return sum(physical_ids.values()) @stub_unexpected_error def get_cpu_model_and_flags(): processor = get_cpu_info()[0] return "{} {}".format( processor.get("model name") or processor["Processor"], processor.get("flags") or processor["Features"], ) @stub_unexpected_error async def get_hosting_panel_version(hp): return await hp.version() @stub_unexpected_error async def get_users_amount(hp): return await hp.users_count() @stub_unexpected_error async def get_domains_amount(hp): return len(await hp.get_domain_to_owner()) @stub_unexpected_error def get_malware_db_update_time(): if os.path.exists(MalwareSignatures.AI_BOLIT_HOSTER): return int(os.path.getmtime(MalwareSignatures.AI_BOLIT_HOSTER)) @stub_unexpected_error async def get_nfm_state(): if await is_native_feature_management_supported(): return await is_native_feature_management_enabled() def get_sha256_machine_id(): machine_id = Path("/etc/machine-id") if machine_id.exists(): return hashlib.sha256(machine_id.read_bytes()).hexdigest() return None @stub_unexpected_error def get_myimunify_whmcs_activation_state(): """ True only if active in whmcs config otherwise False """ activation_state = WhmcsConf().read().get("status") return activation_state == "active" async def get_additional_info(hp): return { "cpu_cores": get_cpu_cores(), "cpuinfo": get_cpu_model_and_flags(), "hosting_panel_version": await get_hosting_panel_version(hp), "users_amount": await get_users_amount(hp), "domains_amount": await get_domains_amount(hp), "trim_malicious": Malware.CLEANUP_TRIM, "days_to_keep_backup": Malware.CLEANUP_KEEP, "malware_db_update_time": get_malware_db_update_time(), "native_feature_management_enabled": await get_nfm_state(), "machine_id": get_sha256_machine_id(), "myimunify_freemium_flag_exists": os.path.exists( FREEMIUM_FEATURE_FLAG ), "myimunify_whmcs_activated": get_myimunify_whmcs_activation_state(), } async def get_users_configs(hp) -> Dict: """ Return dict that includes users config values that are explicitly set in the corresponding config files. """ result = dict() try: current_users = frozenset(await hp.get_users()) except Exception: logger.exception("Failed to get the list of panel's users.") current_users = frozenset() users_conf = os.path.join("*", Core.USER_CONFIG_FILE_NAME) for userconf_file in Path(Core.USER_CONFDIR).glob(users_conf): if userconf_file.parent.name in current_users: result[userconf_file.parent.name] = ConfigFile( username=userconf_file.parent.name ).config_to_dict(normalize=False) return result class SendServerConfig(MessageSink, MessageSource): """ This plugin is to provide central server with different server metrics. Message is sent on plugin creation, and then every :period: seconds """ SCOPE = Scope.AV def __init__(self, period=None): self._task = None # timestamp of the last ConfigUpdate sent to the server self._last_send_time = None # avoid duplicate on startup if period: self._period = period else: self._period = int_from_envvar( "IMUNIFY360_SEND_SERVER_CONFIG_PERIOD", int(datetime.timedelta(days=1).total_seconds() / 3), ) async def create_sink(self, loop): """MessageSink method""" @expect(MessageType.ConfigUpdate) @log_error_and_ignore() async def on_config_update_message(self, message): if self._last_send_time is None: # 1st ConfigUpdate # enable sending config on 2nd+ ConfigUpdate self._last_send_time = 0 return if not isinstance(message["conf"], SystemConfig): # ignore user configs, we do not need to send them on each change # all user configs will be send in # recurring_check(self._period)(self._send_server_config)() return if message["conf"].modified_since(self._last_send_time): self._last_send_time = message["timestamp"] self._loop.create_task(self._send_server_config()) async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._task = self._loop.create_task( recurring_check(self._period)(self._send_server_config)() ) async def shutdown(self): if self._task is not None: self._task, t = None, self._task await safe_cancel_task(t) async def _create_server_config_msg(self): msg = MessageType.ServerConfig(uname=_uname_info()) diskstat = _diskstat() if diskstat: msg["diskstats"] = diskstat hp = HostingPanel() license_info = LicenseCLN.license_info() msg.update(sentry.tags()) msg.update(await get_additional_info(hp)) if hp.NAME == cPanel.NAME: msg["users"] = await get_myimunify_users() msg["iaid"] = IndependentAgentIDAPI.get_iaid() msg["status_license"] = LicenseCLN.is_valid() msg["system_info"] = { "uptime_since": await _uptime(), "devices": await _blkid(), "mac": await _mac_address(), } msg["agent_global_config"] = ( ConfigFile().config_to_dict() | CustomBillingConfig().config_to_dict() ) msg["agent_users_configs"] = await get_users_configs(hp) msg["paths"] = await _get_path_sizes(CH_PATHS) msg["components_versions"] = await system_packages_info( PACKAGES_TO_REPORT ) msg["agent_global_config"][ "CUSTOM_BILLING.effective_upgrade_url" ] = license_info.get("upgrade_url") msg["agent_global_config"][ "CUSTOM_BILLING.effective_upgrade_url_360" ] = license_info.get("upgrade_url_360") msg["agent_global_config"]["CORE.doctor_report"] = load_state( "doctor_key" ).get("doctor_key") save_state("doctor_key", {"doctor_key": None}) return msg async def _send_server_config(self): await self._sink.process_message( await self._create_server_config_msg() ) async def _get_path_sizes(paths) -> Dict[str, int]: """Return path->size mapping for *paths*. Send -errno on error. """ sizes = {} for path in map(os.fspath, paths): try: if os.path.isdir(path): size = _compute_dir_size(path) else: size = os.path.getsize(path) except OSError as e: logger.warning("Can't get size for %s, reason: %s", path, e) sizes[path] = -e.errno else: sizes[path] = size return sizes def _compute_dir_size(directory_path: str) -> int: total_size = 0 def _onerror(err: OSError): raise err for root, _dirs, files in os.walk( directory_path, onerror=_onerror, followlinks=False ): for file_name in files: file_path = os.path.join(root, file_name) try: total_size += os.path.getsize(file_path) except OSError as e: raise e return total_size def _diskstat(): try: with open("/proc/diskstats") as f: return f.read() except OSError as e: # pragma: no cover logger.warning("Can't get diskstat: %s", str(e)) def _uname_info() -> dict: return dict( zip( ("sysname", "nodename", "release", "version", "machine"), os.uname(), ) ) async def _uptime() -> str: """System up since""" return await safe_run(["uptime", "--since"]) async def _blkid() -> str: """Executes utility to locate/print block device attributes""" return await safe_run(["blkid"]) async def _mac_address() -> str: """MAC address in formatted way, like it specifies in /sys/class/net/*/address""" return binascii.hexlify(uuid.getnode().to_bytes(6, "big"), ":").decode() defence360agent/plugins/service_manager.py0000644000000000000000000000417700000000000015744 0ustar """Base service manager plugin. Provides the shared start/stop/enable/disable logic that product-specific service managers (imav, im360) inherit from. """ import asyncio import logging from defence360agent import utils from defence360agent.contracts import messages, plugins logger = logging.getLogger(__name__) class BaseServiceManager(plugins.MessageSink): """Base service manager: start/stop services based on config changes. Subclasses populate ``_services`` (list of async check callables) and ``_units`` (dict of name → unitctl) in their ``__init__``. """ def __init__(self): self._lock = asyncio.Lock() self._services = [] self._units = {} async def _ensure_consistent_services_state(self): for service in self._services: await service() @plugins.expect(messages.MessageType.ConfigUpdate) async def on_config_update( self, message_ignored: messages.MessageType.ConfigUpdate ): async with self._lock: await self._ensure_consistent_services_state() @utils.log_error_and_ignore() async def _ensure_service_status( self, unitctl, service_name, should_be_running, reload=False ): is_running = await unitctl.is_active() if is_running is not should_be_running: if should_be_running: logger.info( "%s is enabled in the config but it is not" " running. Enabling it...", service_name, ) await unitctl.enable(now=True) logger.info("Enabled %s", service_name) else: logger.info( "%s is not enabled in the config but it is" " running. Disabling it...", service_name, ) await unitctl.disable(now=True) logger.info("Disabled %s", service_name) else: if is_running and reload: await unitctl.reload() logger.info( "Reloading %s after config update...", service_name ) defence360agent/plugins/wordpress.py0000644000000000000000000010667300000000000014646 0ustar import asyncio import logging import pwd from contextlib import suppress from pathlib import Path from typing import Coroutine from defence360agent.contracts.config import ( ANTIVIRUS_MODE, ConfigValidationError, SystemConfig, UserConfig, Wordpress, ) from defence360agent.contracts.hook_events import HookEvent from defence360agent.contracts.license import LicenseCLN from defence360agent.contracts.messages import MessageType from defence360agent.contracts.plugins import ( MessageSink, MessageSource, expect, ) from defence360agent.subsys.panels import hosting_panel from defence360agent.subsys.persistent_state import ( load_state, register_lock_file, save_state, ) from defence360agent.utils import ( Scope, importer, recurring_check, system_packages_info, ) from defence360agent.utils.check_lock import check_lock from defence360agent.internals.iaid import IndependentAgentIDAPI from defence360agent.utils.common import DAY from defence360agent.wordpress import cli as wp_cli from defence360agent.wordpress import plugin from defence360agent.wordpress.utils import _prepare_ai_bot_settings from defence360agent.wordpress.bot_protection import ( resolve_ai_bot_protection, ) from defence360agent.model import tls_check from defence360agent.model.wordpress import WPSite, WordpressSite from defence360agent.wordpress.site_repository import ( get_sites_by_path, get_sites_for_user, get_installed_sites, ) from defence360agent.wordpress.proxy_auth import ( is_secret_expired, rotate_secret, ) from defence360agent.wordpress import ( ChangelogProcessor, IncidentCollector, IncidentSender, ) from defence360agent.wordpress.plugin import update_disabled_rules_on_sites from defence360agent.model.wordpress_incident import ( delete_old_wordpress_incidents, ) logger = logging.getLogger(__name__) LOCK_FILE = register_lock_file("wp-gen-auth", Scope.AV_IM360) SITE_PROCESSING_LOCK_FILE = register_lock_file( "wp-site-process", Scope.AV_IM360 ) SEND_WP_PLUGIN_STATS_LOCK_FILE = register_lock_file( "wp-plugin-stats", Scope.AV_IM360 ) LICENSE_RECONVERGE_LOCK_FILE = register_lock_file( "wp-license-reconverge", Scope.AV_IM360 ) CONFIG_DIR = Path("/etc/sysconfig/imunify360/imunify360.config.d") FIRST_INSTALL_CONFIG_FILE = Path( "/opt/imunify360/venv/share/imunify360/11_on_first_install_wp_av.config" ) FIRST_INSTALL_CONFIG_PATH = CONFIG_DIR / "11_on_first_install_wp_av.config" FIRST_INSTALL_FLAG = CONFIG_DIR / ".11_on_first_install_wp_av.flag" _MalwareHit = importer.get( module="imav.malwarelib.model", name="MalwareHit", default=None ) def _get_cleaned_malware_hits(started_timestamp: float) -> list: """ Get malware hits cleaned since the given timestamp with lazy import fallback. Returns empty list if imav.malwarelib is not available. """ if _MalwareHit is None: logger.debug( "imav.malwarelib not available, returning empty cleaned hits" ) return [] return _MalwareHit.cleaned_since(started_timestamp) class ImunifySecurityPlugin(MessageSink, MessageSource): SCOPE = Scope.AV_IM360 def __init__(self): self._loop = None self._sink = None state = load_state("ImunifySecurityPlugin") self.installation_completed = state.get("installed") # Explicit None check: a persisted False must win over the live # config value. Plain `or` would flip False → True via short-circuit. persisted_enabled = state.get("enabled") self.last_config_value = ( persisted_enabled if persisted_enabled is not None else Wordpress.SECURITY_PLUGIN_ENABLED ) self._last_waf_enabled = plugin._get_global_waf_enabled() self._last_waf_default = plugin._get_waf_default() self._last_user_waf_enabled: dict[str, bool | None] = {} # Seed with the current value so the first ConfigUpdate after startup # only triggers a propagation when the admin has actually toggled it. self._last_ai_bot_protection = plugin._get_global_ai_bot_protection() self._last_ai_bot_protection_preset = ( plugin._get_global_ai_bot_protection_preset() ) # Seeded None (not a live read) to keep license-file I/O out of the # constructor; the poll reconverges on the first tick if it differs. self._last_license_type = None self.installation_task: asyncio.Task | None = None self.deleting_task: asyncio.Task | None = None self.install_and_update_task: asyncio.Task | None = None self.freshly_installed_sites: set[WPSite] = set() # Incident collection and changelog processing components self.incident_collector = IncidentCollector() self.incident_sender = IncidentSender() self.changelog_processor = ChangelogProcessor() self._site_processing_task: asyncio.Task | None = None self._stats_task: asyncio.Task | None = None self._license_reconverge_task: asyncio.Task | None = None async def create_sink(self, loop): pass async def create_source(self, loop, sink): self._loop = loop self._sink = sink self._update_auth_task = self._loop.create_task( self.refresh_auth_files() ) self._site_processing_task = self._loop.create_task( self.process_wordpress_sites() ) self._stats_task = self._loop.create_task(self.send_stats()) self._license_reconverge_task = self._loop.create_task( self.reconverge_license_type() ) if ANTIVIRUS_MODE: await self._apply_first_install_config() else: FIRST_INSTALL_FLAG.unlink(missing_ok=True) await self._recover_installation_on_startup() async def _recover_installation_on_startup(self): """ Self-heal when the installation state was lost. If the feature is enabled but installation_completed is falsy (state file missing, earlier install interrupted, etc.), manage_plugin_installation can never recover: its True == True guard always returns early. Trigger install_everywhere once per restart to repopulate the wordpress_site table and flip the flag. """ if ( self.installation_completed or not Wordpress.SECURITY_PLUGIN_ENABLED ): return logger.info( "Installation state is missing while feature is enabled; " "triggering startup self-recovery" ) # Sync last_config_value to the live config. If the persisted state # held a stale `enabled: False`, _mark_installation_done would bail # on `if not self.last_config_value` and installation_completed would # never flip — recovery would re-run on every restart. self.last_config_value = True await self.process_installation( plugin.install_everywhere(sink=self._sink) ) if self.installation_task is not None: self.installation_task.add_done_callback( self._mark_installation_done ) async def _apply_first_install_config(self): if not FIRST_INSTALL_FLAG.exists(): return if await hosting_panel.HostingPanel().users_count() == 1: _ = FIRST_INSTALL_CONFIG_PATH.write_text( FIRST_INSTALL_CONFIG_FILE.read_text() ) FIRST_INSTALL_CONFIG_PATH.chmod(0o600) FIRST_INSTALL_FLAG.unlink() async def shutdown(self): self._update_auth_task.cancel() # CancelledError is handled by @recurring_check(): await self._update_auth_task # Cancel site processing (changelogs + incidents) task if self._site_processing_task: self._site_processing_task.cancel() await self._site_processing_task if self._stats_task: self._stats_task.cancel() await self._stats_task if self._license_reconverge_task: self._license_reconverge_task.cancel() await self._license_reconverge_task def _task_in_progress(self, task_attr_name): if not hasattr(self, task_attr_name): logger.error("Unknown task '%s'", task_attr_name) return False task = getattr(self, task_attr_name) return task is not None and not task.done() and not task.cancelled() def _save_installation_state(self): save_state( "ImunifySecurityPlugin", { "installed": self.installation_completed, "enabled": self.last_config_value, }, ) def _mark_installation_done(self, task: asyncio.Task): if task.cancelled(): logger.info("Installation task was cancelled") return exc = task.exception() if exc is not None: logger.error("Installation task failed: %s", exc) return if not self.last_config_value: logger.info( "Feature was disabled during installation, " "skipping flag update" ) return self.installation_completed = True self._save_installation_state() async def process_installation(self, coro: Coroutine, for_new_sites=False): if self._task_in_progress("deleting_task"): if for_new_sites: coro.close() return if self.deleting_task: self.deleting_task.cancel() try: await self.deleting_task except asyncio.CancelledError: pass if self._task_in_progress("installation_task"): logger.warning("Installation is already running") coro.close() return self.installation_task = asyncio.create_task(coro) async def process_deleting(self, coro): if self._task_in_progress("installation_task"): if self.installation_task: self.installation_task.cancel() try: await self.installation_task except asyncio.CancelledError: pass if self._task_in_progress("deleting_task"): logger.warning("Deleting is already running") return self.deleting_task = asyncio.create_task(coro) @recurring_check( check_lock, check_period_first=True, check_lock_period=DAY, lock_file=LOCK_FILE, ) async def refresh_auth_files(self): if is_secret_expired(): await rotate_secret() await plugin.update_auth_everywhere(sink=self._sink) @recurring_check( check_lock, check_period_first=True, jitter=True, check_lock_period=DAY, lock_file=SEND_WP_PLUGIN_STATS_LOCK_FILE, ) async def send_stats(self): """Send WP plugin adoption stats to the correlation server.""" if not Wordpress.SECURITY_PLUGIN_ENABLED: return sites = await self._loop.run_in_executor(None, get_installed_sites) def _count_manually_removed(): with suppress(tls_check.OverridingReset): tls_check.reset() return ( WordpressSite.select() .where(WordpressSite.manually_deleted_at.is_null(False)) .count() ) manually_removed = await self._loop.run_in_executor( None, _count_manually_removed ) waf_enabled_sites = 0 ai_bot_enabled_sites = 0 preset_counts = {"balanced": 0, "strict": 0, "monitor": 0} user_ai_config: dict[int, dict] = {} for site in sites: content_dir = await wp_cli.get_content_dir(site) data_dir = content_dir / "imunify-security" rules_php = data_dir / "rules.php" if await self._loop.run_in_executor(None, rules_php.exists): waf_enabled_sites += 1 if site.uid not in user_ai_config: try: pw_record = await self._loop.run_in_executor( None, pwd.getpwuid, site.uid ) user_ai_config[ site.uid ] = await self._loop.run_in_executor( None, _prepare_ai_bot_settings, pw_record.pw_name, ) except Exception as exc: logger.info( "Could not load AI bot protection config for uid" " %s, counting it as disabled: %s", site.uid, exc, ) user_ai_config[site.uid] = { "ai_bot_protection": False, "preset": "balanced", } hoster_cfg = user_ai_config[site.uid] ai_enabled, preset = await self._loop.run_in_executor( None, resolve_ai_bot_protection, site.docroot, data_dir, site.uid, bool(hoster_cfg.get("ai_bot_protection")), hoster_cfg.get("preset", "balanced"), ) if ai_enabled: ai_bot_enabled_sites += 1 if preset in preset_counts: preset_counts[preset] += 1 pkgs = await system_packages_info( { "imunify360-firewall", "imunify-antivirus", "imunify-core", "imunify-wp-security", } ) av_version = pkgs.get("imunify-antivirus") or "" firewall_version = pkgs.get("imunify360-firewall") or "" core_version = pkgs.get("imunify-core") or "" wp_version = pkgs.get("imunify-wp-security") or "" msg = MessageType.WpSecurityPluginStats( core_version=core_version, av_version=av_version, firewall_version=firewall_version, wp_version=wp_version, installed_sites=len(sites), manually_removed_sites=manually_removed, server_config={ "waf_enabled": str(plugin._get_global_waf_enabled()), "ai_bot_protection": str( plugin._get_global_ai_bot_protection() ), }, stats={ "waf_enabled_sites": str(waf_enabled_sites), "ai_bot_protection_enabled_sites": str(ai_bot_enabled_sites), "ai_bot_protection_preset_balanced": str( preset_counts["balanced"] ), "ai_bot_protection_preset_strict": str( preset_counts["strict"] ), "ai_bot_protection_preset_monitor": str( preset_counts["monitor"] ), }, ) msg["iaid"] = await self._loop.run_in_executor( None, IndependentAgentIDAPI.get_iaid ) await self._sink.process_message(msg) @recurring_check( check_lock, check_period_first=True, check_lock_period=1 * 60, # Run every 1 minute lock_file=SITE_PROCESSING_LOCK_FILE, ) async def process_wordpress_sites(self): """ Periodic task for WordPress site file processing. Runs every minute to: 1. Process changelog.php files written by the WordPress plugin (rule disable/enable from WP admin) 2. Collect incident files written by the WordPress plugin """ logger.debug( "Processing rule disable changelogs" " and collecting WordPress CVE protection incidents" ) try: await self._process_installed_sites() except Exception as e: # deliberately not fatal to the pass below: that pass is the # repair path for incidents an earlier cycle failed to deliver, # and a collection that keeps failing must not strand them logger.error("Error in WordPress site processing: %s", e) try: # sends the freshly collected incidents together with any whose # earlier message the transport never acknowledged. Reads the # database, not the filesystem, so it must run even with no sites # left: otherwise removing the last site strands whatever the # transport had not yet confirmed. await self.incident_sender.send_pending_incidents(self._sink) except Exception as e: logger.error("Error sending pending WordPress incidents: %s", e) async def _process_installed_sites(self): sites = get_installed_sites() if not sites: logger.debug("No WordPress sites found for periodic processing") return # Process changelogs (rule disable/enable from WordPress admin) affected_sites = ( await self.changelog_processor.process_changelogs_for_sites( sites, self._sink ) ) if affected_sites: await update_disabled_rules_on_sites( domains=[s.domain for s in affected_sites], sink=self._sink, ) # Collect incidents await self.incident_collector.collect_incidents_for_sites( sites, delete_after_processing=True, ) delete_old_wordpress_incidents(days=30) async def _install_on_new_sites(self): """Install plugin on new WordPress sites.""" # Clear any previously tracked sites self.freshly_installed_sites.clear() async def install_and_track(): installed_sites = await plugin.install_everywhere(sink=self._sink) if installed_sites: self.freshly_installed_sites.update(installed_sites) return installed_sites await self.process_installation( install_and_track(), for_new_sites=True, ) async def _tidy_up(self): """Tidy up sites from which the WordPress plugin was deleted manually.""" await plugin.tidy_up_manually_deleted( sink=self._sink, freshly_installed_sites=self.freshly_installed_sites, ) await plugin.fix_data_file_permissions_everywhere(sink=self._sink) if not Wordpress.SECURITY_PLUGIN_ENABLED: await self.process_deleting( plugin.remove_all_installed(sink=self._sink) ) self.installation_completed = False self.last_config_value = False self._save_installation_state() async def _adopt_found_sites(self): """Adopt sites where plugin is installed but not tracked in our database.""" adopted_sites = await plugin.adopt_found_sites(sink=self._sink) # Add adopted sites to freshly_installed_sites to prevent them from being # marked as manually deleted by tidy_up (AVD database may not be updated yet) if adopted_sites: self.freshly_installed_sites.update(adopted_sites) async def _update_existing(self): """Update plugin on all sites where it is installed.""" await plugin.update_everywhere(sink=self._sink) async def _run_install_and_update(self): """ Combined operation: install on new sites, adopt found sites, tidy up, and update existing plugins. This runs all operations sequentially to avoid race conditions. """ # Install plugin on new sites. await self._install_on_new_sites() # Wait for installation to complete before proceeding. if self.installation_task: await self.installation_task # Adopt sites where plugin is installed but not in our database. await self._adopt_found_sites() # Tidy up and update. await self._tidy_up() await self._update_existing() @expect(MessageType.WordpressPluginAction) async def manage_plugin_action(self, message): logger.info( "ImunifySecurityPlugin received message action: %s method: %s", message.action, message.method, ) # Check if install_and_update is running - it blocks all other actions if self._task_in_progress("install_and_update_task"): logger.warning( "Install-and-update is still running, skipping action %s", message.action, ) return if message.action == "install_on_new_sites": if not self.installation_completed: # The installation is not completed yet. We cannot know reliably which sites are new. return await self._install_on_new_sites() return if self._task_in_progress("installation_task"): logger.warning( "Installation is still running, skipping action %s", message.action, ) return if self._task_in_progress("deleting_task"): logger.warning( "Uninstallation is already running, skipping action %s", message.action, ) return if message.action == "update_existing": await self._update_existing() return if message.action == "tidy_up": await self._tidy_up() return if message.action == "install_and_update": if not self.installation_completed: # The installation is not completed yet. We cannot know reliably which sites are new. logger.warning( "Installation is not completed yet, skipping" " install_and_update" ) return # Run install_and_update as a background task to prevent blocking. # Note: No need to check if already running - the check at the top of this function # (line 182) already handles that case. self.install_and_update_task = asyncio.create_task( self._run_install_and_update() ) @expect(MessageType.ConfigUpdate) async def manage_plugin_installation(self, message): if not isinstance(message["conf"], SystemConfig): return current_config_value = Wordpress.SECURITY_PLUGIN_ENABLED if current_config_value == self.last_config_value: return # Update last config value immediately to prevent multiple installations self.last_config_value = current_config_value if current_config_value and not self.installation_completed: # On re-enable, force waf_enabled back on and ai_bot_protection # back off so a value that went stale while the plugin was off # cannot take effect silently. Skip a key the operator set in this # same update — overwriting it here would discard their explicit # choice with no warning. The file-poll path (config_watcher) # carries no delta, so it still resets both. submitted_wp = (message.get("submitted") or {}).get( "WORDPRESS", {} ) if "waf_enabled" not in submitted_wp: try: SystemConfig().dict_to_config( {"WORDPRESS": {"waf_enabled": True}} ) self._last_waf_enabled = True except ConfigValidationError: logger.debug( "waf_enabled config reset skipped," " field not in schema yet" ) else: # Record the operator's value so manage_waf_config, which runs # next on this same message, sees no change and does not start # an all-sites WAF removal/redeploy that races the installer # started below. self._last_waf_enabled = plugin._get_global_waf_enabled() if "ai_bot_protection" not in submitted_wp: try: SystemConfig().dict_to_config( {"WORDPRESS": {"ai_bot_protection": False}} ) self._last_ai_bot_protection = False self._last_ai_bot_protection_preset = ( plugin._get_global_ai_bot_protection_preset() ) except ConfigValidationError: logger.debug( "ai_bot_protection config reset skipped," " field not in schema yet" ) else: self._last_ai_bot_protection = ( plugin._get_global_ai_bot_protection() ) self._last_ai_bot_protection_preset = ( plugin._get_global_ai_bot_protection_preset() ) await self.process_installation( plugin.install_everywhere(sink=self._sink) ) if self.installation_task is not None: self.installation_task.add_done_callback( self._mark_installation_done ) elif not current_config_value and ( self.installation_completed or self._task_in_progress("installation_task") ): await self.process_deleting( plugin.remove_all_installed(sink=self._sink) ) self.installation_completed = False self._save_installation_state() @expect(MessageType.ConfigUpdate) async def manage_ai_bot_protection_config(self, message): """ Propagate admin toggles of WORDPRESS.ai_bot_protection and WORDPRESS.ai_bot_protection_preset to every managed WP install's plugin_config.php immediately, so the WP plugin picks up the change at the next request rather than waiting for a scan cycle. Phase 2 per-account support extends *this* handler with a UserConfig branch (mirroring manage_waf_config); do not add a sibling handler. """ if not isinstance(message["conf"], SystemConfig): return if not Wordpress.SECURITY_PLUGIN_ENABLED: # manage_plugin_installation clears any stale value on the # next plugin re-enable, so nothing to write here. return if not self.installation_completed: # Plugin is being (re-)installed. manage_plugin_installation has # already settled ai_bot_protection (reset to False, or left as # the operator submitted it) and the install flow writes # plugin_config.php for every site, so propagating here would race # with it and leave stale values on sites the installer skips # (e.g. DB rows surviving a crash during a prior disable). return current_enabled = plugin._get_global_ai_bot_protection() current_preset = plugin._get_global_ai_bot_protection_preset() if ( current_enabled == self._last_ai_bot_protection and current_preset == self._last_ai_bot_protection_preset ): return sites = await self._loop.run_in_executor(None, get_installed_sites) if not sites: self._last_ai_bot_protection = current_enabled self._last_ai_bot_protection_preset = current_preset return written = await plugin.update_plugin_config_on_sites(sites) if written == len(sites): self._last_ai_bot_protection = current_enabled self._last_ai_bot_protection_preset = current_preset @recurring_check( check_lock, check_period_first=True, check_lock_period=1 * 60, lock_file=LICENSE_RECONVERGE_LOCK_FILE, ) async def reconverge_license_type(self): """Poll for license-edition changes and reconverge plugin_config.php. Edition changes reach only the external hook framework, never the message bus, so a poll is the propagation trigger. """ await self._reconverge_license_type_once() async def _reconverge_license_type_once(self): if not Wordpress.SECURITY_PLUGIN_ENABLED: return if not self.installation_completed: return current = LicenseCLN.get_license_type() if current == self._last_license_type: return sites = await self._loop.run_in_executor(None, get_installed_sites) if not sites: self._last_license_type = current return written = await plugin.update_plugin_config_on_sites(sites) if written == len(sites): self._last_license_type = current @expect(MessageType.ConfigUpdate) async def manage_waf_config(self, message): """Caches are dispatch markers, not apply receipts — apply failures propagate, no auto-retry.""" if not Wordpress.SECURITY_PLUGIN_ENABLED: return conf = message["conf"] config_dict = conf.config_to_dict() waf_value = config_dict.get("WORDPRESS", {}).get("waf_enabled") if isinstance(conf, UserConfig): if waf_value is None: prev = self._last_user_waf_enabled.pop(conf.username, None) if prev is None: return new_effective = await plugin.is_waf_enabled_for_user( conf.username ) if not prev and new_effective: await plugin.redeploy_waf_for_user(conf.username) elif prev and not new_effective: await plugin.remove_waf_rules_for_user(conf.username) return if waf_value == self._last_user_waf_enabled.get(conf.username): return self._last_user_waf_enabled[conf.username] = waf_value if not waf_value or not plugin._get_global_waf_enabled(): await plugin.remove_waf_rules_for_user(conf.username) else: await plugin.redeploy_waf_for_user(conf.username) elif isinstance(conf, SystemConfig): try: current = Wordpress.WAF_ENABLED except KeyError: pass else: if current != self._last_waf_enabled: self._last_waf_enabled = current if not current: await plugin.remove_waf_rules_for_all_sites() else: await plugin.redeploy_waf_for_all_sites() try: current_default = Wordpress.WAF_DEFAULT except KeyError: pass else: if current_default != self._last_waf_default: self._last_waf_default = current_default await plugin.apply_waf_default_change() @expect(HookEvent.MalwareCleanupFinished) async def handle_malware_cleanup_finished(self, message): """ INFO [2025-02-24 12:00:20,384] imav.plugins.wordpress: Malware cleanup finished: HookEvent.MalwareCleanupFinished( { 'cleanup_id': 'fa4fe7e48dbf45588f53b24366cd8893', 'started': 1740398411.786418, 'error': None, 'total_files': 3, 'total_cleaned': 3, 'status': 'ok' } ) """ # Skip if plugin is disabled if not self.last_config_value: return # Leave early if status is not ok or the started time is missing. if message.get("status") != "ok" or not message.get("started"): return # load all malware hits cleaned since the cleanup started hits = _get_cleaned_malware_hits(message["started"]) site_paths = set() # Collect all site paths that need to be updated. for hit in hits: if hit.resource_type == "file": try: user_info = pwd.getpwnam(hit.user) user_sites = get_sites_for_user(user_info) uid = ( # In None cases there also no user_sites, so it wouldn't be used user_info.pw_uid if user_info else None ) for site_path in user_sites: if hit.orig_file.startswith(site_path): site_paths.add((site_path, uid)) break except KeyError: pass if not site_paths: logger.debug("Cleanup finished => no sites found for cleaned hits") return logger.info( "Cleanup finished => %s site(s) need to be updated", len(site_paths), ) # Convert paths to WPSite objects with empty domain and update data on the sites that need to be updated. # We need to work with paths here because sometimes the domain is not set, see https://cloudlinux.atlassian.net/browse/DEF-32238. wordpress_sites = [ WPSite(docroot=site_path, domain="", uid=uid) for site_path, uid in site_paths ] await plugin.update_data_on_sites(self._sink, wordpress_sites) logger.info("%s site(s) updated after a cleanup", len(wordpress_sites)) @expect(HookEvent.MalwareScanningFinished) async def handle_malware_scan_finished(self, message): """ INFO [2025-02-24 11:57:17,968] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'b9bd136aff0a4d87a248c859cfe41c47', 'scan_type': 'user', 'path': '/home/user1' } ) INFO [2025-02-24 12:00:10,740] imav.plugins.wordpress: Malware scan finished: HookEvent.MalwareScanningFinished( { 'scan_id': 'a74271d2cdd04e0c9bd49ef6de23e0d8', 'scan_type': 'user', 'path': '/home/user4', 'started': 1740398383, 'total_files': 39229, 'total_malicious': 3, 'error': None, 'status': 'ok', 'scan_params': {'intensity_cpu': 2, 'intensity_io': 2, 'intensity_ram': 2048, 'initiator': None, 'file_patterns': None, 'exclude_patterns': None, 'follow_symlinks': False, 'detect_elf': True}, 'stats': {'scan_time': 27, 'mem_peak': 28217344, 'smart_time_hs': 0.004, 'scan_time_hs': 1.1751, 'smart_time_preg': 0, 'scan_time_preg': 2.7391, 'finder_time': 13.5896, 'cas_time': 0.7562, 'deobfuscate_time': 0.8998, 'total_files': 39229} } ) """ # Skip if plugin is disabled if not self.last_config_value: return # Leave early if status is not ok or path or stats are missing. if ( message.get("status") != "ok" or not message.get("path") or not message.get("stats") ): return # Malware scan is finished, figure out what sites need to be updated based on the path. path = message["path"] sites = get_sites_by_path(path) if not sites: logger.debug("Scan finished => no sites found for path=%s", path) return # Update data on the sites that need to be updated. logger.info( "Scan finished => %s site(s) need to be updated", len(sites) ) await plugin.update_data_on_sites(self._sink, sites) logger.info("%s site(s) updated after a scan", len(sites)) defence360agent/router.py0000644000000000000000000000322600000000000012443 0ustar """Provide Router for db migrations.""" import os from contextlib import suppress from peewee_migrate import Router as PeeweeRouter from peewee_migrate.router import void __all__ = ["Router"] class Router(PeeweeRouter): """Like peewee_migrate.Router but supports multiple migrations dirs.""" # this is a slightly edited version from peewee_migrate.router.Router def __init__(self, database, migrations_dirs, **kwargs): super().__init__(database, migrate_dir=migrations_dirs[0], **kwargs) self.migrations_dirs = migrations_dirs @property def todo(self): """Scan migrations in file system.""" for migrate_dir in self.migrations_dirs: if not os.path.exists(migrate_dir): self.logger.warn( "Migration directory: %s does not exist.", migrate_dir ) os.makedirs(migrate_dir) migration_names = [] for migrate_dir in self.migrations_dirs: migration_names += sorted( f[: -len(".py")] for f in os.listdir(migrate_dir) if self.filemask.match(f) ) return migration_names def read(self, name): """Read migration from file.""" scope = {} for migrate_dir in self.migrations_dirs: with suppress(FileNotFoundError): with open(os.path.join(migrate_dir, name + ".py")) as f: code = compile( f.read(), "", "exec", dont_inherit=True ) exec(code, scope) return scope.get("migrate", void), scope.get("rollback", void) defence360agent/rpc_tools/0000755000000000000000000000000000000000000012552 5ustar defence360agent/rpc_tools/__init__.py0000644000000000000000000000146000000000000014664 0ustar """ RPC building blocks. Use the utils provided by this package whenever you need to extend the RPC client/server functionality (i.e. add a new endpoint). However, new endpoints should not be put in this package to avoid undesirable interdependencies. """ from contextvars import ContextVar from defence360agent.utils.cli import ERROR, SUCCESS, WARNING from .exceptions import ResponseError, ServiceStateError, SocketError from .lookup import Endpoints, UserType from .utils import is_running from .validate import ValidationError caller_uid_var: ContextVar[int] = ContextVar("rpc_caller_uid") __all__ = [ "ERROR", "SUCCESS", "WARNING", "caller_uid_var", "ResponseError", "ServiceStateError", "SocketError", "Endpoints", "UserType", "is_running", "ValidationError", ] defence360agent/rpc_tools/__pycache__/0000755000000000000000000000000000000000000014762 5ustar defence360agent/rpc_tools/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000227500000000000022170 0ustar r_j0UdZddlmZddlmZmZmZddlmZm Z m Z ddl m Z m Z ddlmZddlmZed Zeeed <gd Zd S) z RPC building blocks. Use the utils provided by this package whenever you need to extend the RPC client/server functionality (i.e. add a new endpoint). However, new endpoints should not be put in this package to avoid undesirable interdependencies. ) ContextVar)ERRORSUCCESSWARNING) ResponseErrorServiceStateError SocketError) EndpointsUserType) is_running)ValidationErrorrpc_caller_uidcaller_uid_var) rrrrrr r r r r rN)__doc__ contextvarsrdefence360agent.utils.clirrr exceptionsrr r lookupr r utilsr validaterrint__annotations____all__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/__init__.pyrs#"""""==========EEEEEEEEEE''''''''%%%%%%",*-=">"> 3>>>   rdefence360agent/rpc_tools/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000227500000000000021231 0ustar r_j0UdZddlmZddlmZmZmZddlmZm Z m Z ddl m Z m Z ddlmZddlmZed Zeeed <gd Zd S) z RPC building blocks. Use the utils provided by this package whenever you need to extend the RPC client/server functionality (i.e. add a new endpoint). However, new endpoints should not be put in this package to avoid undesirable interdependencies. ) ContextVar)ERRORSUCCESSWARNING) ResponseErrorServiceStateError SocketError) EndpointsUserType) is_running)ValidationErrorrpc_caller_uidcaller_uid_var) rrrrrr r r r r rN)__doc__ contextvarsrdefence360agent.utils.clirrr exceptionsrr r lookupr r utilsr validaterrint__annotations____all__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/__init__.pyrs#"""""==========EEEEEEEEEE''''''''%%%%%%",*-=">"> 3>>>   rdefence360agent/rpc_tools/__pycache__/exceptions.cpython-311.opt-1.pyc0000644000000000000000000000335100000000000022606 0ustar r_jddlmZGddeZGddeZGddeZGdd eZGd d eZd S) )configceZdZdS)RpcErrorN__name__ __module__ __qualname__Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/exceptions.pyrrDr rceZdZdS) ResponseErrorNrr r r rrr r rceZdZdS) SocketErrorNrr r r rr r r rc eZdZdfd ZxZS)ServiceStateErrorstoppedctdtjj|dS)Nz{} service is {}.)super__init__formatrCorePRODUCT)selfstate __class__s r rzServiceStateError.__init__sA   & &v{':E B B     r )r)rrr r __classcell__)rs@r rrs=          r rceZdZdS)NonRootValidationErrorNrr r r r r r r r N)defence360agent.contractsr RuntimeErrorrrrrr r r r r#s,,,,,,     |        H        (                X     r defence360agent/rpc_tools/__pycache__/exceptions.cpython-311.pyc0000644000000000000000000000335100000000000021647 0ustar r_jddlmZGddeZGddeZGddeZGdd eZGd d eZd S) )configceZdZdS)RpcErrorN__name__ __module__ __qualname__Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/exceptions.pyrrDr rceZdZdS) ResponseErrorNrr r r rrr r rceZdZdS) SocketErrorNrr r r rr r r rc eZdZdfd ZxZS)ServiceStateErrorstoppedctdtjj|dS)Nz{} service is {}.)super__init__formatrCorePRODUCT)selfstate __class__s r rzServiceStateError.__init__sA   & &v{':E B B     r )r)rrr r __classcell__)rs@r rrs=          r rceZdZdS)NonRootValidationErrorNrr r r r r r r r N)defence360agent.contractsr RuntimeErrorrrrrr r r r r#s,,,,,,     |        H        (                X     r defence360agent/rpc_tools/__pycache__/lookup.cpython-311.opt-1.pyc0000644000000000000000000001637300000000000021746 0ustar r_j"ddlZddlZddlmZddlmZddlmZddlm Z dZ Gdd e Z Gd d e Z Gd d ZGddeZGddeZGddeZeje fzZeejfdZdZdS)N)Any)UserType)Scope)RpcError __rpc_commandceZdZdS)DuplicateHandlerErrorN__name__ __module__ __qualname__U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/lookup.pyr r Drr ceZdZdS)NotCoroutineErrorNr rrrrrrrrceZdZdZejZeZe j ie j iiZ gZ fdZedZdZee j fdefdZed dZed ZxZS) Endpointsz\Endpoints class implements registration and lookup for functions implementing RPC calls.c ntjdi||j|dS)Nr)super__init_subclass__ _subclassesappend)clskwargs __class__s rrzEndpoints.__init_subclass__!s<!!++F+++ s#####rcxg}|jD]/}tj|d}|r||0|S)Nc.t|tdSN)getattr _RPC_MARK)items rz0Endpoints.get_active_endpoints..+sWT9d%C%Cr)rinspect getmembersr)ractive_endpointssubcls rpc_handlerss rget_active_endpointszEndpoints.get_active_endpoints%s[o 0 0F"-CCL 0 ''///rc||_dSr!)_sink)selfsinks r__init__zEndpoints.__init__1s  rreturnc.K|d}t|}||j|vr+tdd|dz|j||\}}t |||}|di|dd{VS)a:Find appropriate class and function within that class that implements processing for request based on supplied 'command' within. Call that (async) function and return its result. If target class/function for given request['command'] is not found then RpcError exception is raised.commandz&Endpoint not found for RPC method "%s" paramsNr)tuple_Endpoints__COMMAND_MAPrjoinr") rrequestr/userr3key cls_handler handler_namehandlers rroute_to_endpointzEndpoints.route_to_endpoint4s)$Gnn c'- - -8((79-../ %($5d$;C$@! \++d++\::W11wx0111111111rNct|D]}|drt||}t|td}|At j|st d|jD][}||j|vr8d |||j|||}t|||f|j||<\dS)z{Registers RPC handlers for all functions within a class. Functions should be decorated with @bind('command', ...)._NzMust be a coroutinez1Duplicate handlers for command {} ({}): {} and {}) dir startswithr"r#r&iscoroutinefunctionrAPPLICABLE_USER_TYPESr7formatr )rnameattrr3 user_typemsgs rregister_rpc_handlerszEndpoints.register_rpc_handlersHs HH D DDs## 3%%DdIt44G.t44 ?'(=>>> 6 D D c/ :::K#%-i8A 04449rrWc"eZdZdZejhZdS) RootEndpointsz'Endpoints available only for root user.N)r r rrPrrNrErrrrYrYqs 11%]OrrYc"eZdZdZejhZdS)UserOnlyEndpointsz,Endpoints available only for non root users.N)r r rrPrrMrErrrr[r[ws"66%./rr[cFtjtj|||S)z4Decorator replacing functools.wraps for rpc handlerswrappedassignedupdated) functoolspartialupdate_wrapperr]s rwrapsrds-       rcfd}|S)z4Mark a function as processing RPC calls for command.c4t|t|Sr!)setattrr#)funcr3s r decoratorzbind..decoratorsi))) rr)r3ris` rbindrjs$ r)rar&typingr defence360agent.contracts.configrdefence360agent.utilsr exceptionsrr# Exceptionr rrrWrYr[WRAPPER_ASSIGNMENTSLOOKUP_ASSIGNMENTSWRAPPER_UPDATESrdrjrrrrss555555''''''       I           S.S.S.S.S.S.S.S.l?????i??? ,,,,,I,,, 00000 000 2i\A))2K    rdefence360agent/rpc_tools/__pycache__/lookup.cpython-311.pyc0000644000000000000000000001637300000000000021007 0ustar r_j"ddlZddlZddlmZddlmZddlmZddlm Z dZ Gdd e Z Gd d e Z Gd d ZGddeZGddeZGddeZeje fzZeejfdZdZdS)N)Any)UserType)Scope)RpcError __rpc_commandceZdZdS)DuplicateHandlerErrorN__name__ __module__ __qualname__U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/lookup.pyr r Drr ceZdZdS)NotCoroutineErrorNr rrrrrrrrceZdZdZejZeZe j ie j iiZ gZ fdZedZdZee j fdefdZed dZed ZxZS) Endpointsz\Endpoints class implements registration and lookup for functions implementing RPC calls.c ntjdi||j|dS)Nr)super__init_subclass__ _subclassesappend)clskwargs __class__s rrzEndpoints.__init_subclass__!s<!!++F+++ s#####rcxg}|jD]/}tj|d}|r||0|S)Nc.t|tdSN)getattr _RPC_MARK)items rz0Endpoints.get_active_endpoints..+sWT9d%C%Cr)rinspect getmembersr)ractive_endpointssubcls rpc_handlerss rget_active_endpointszEndpoints.get_active_endpoints%s[o 0 0F"-CCL 0 ''///rc||_dSr!)_sink)selfsinks r__init__zEndpoints.__init__1s  rreturnc.K|d}t|}||j|vr+tdd|dz|j||\}}t |||}|di|dd{VS)a:Find appropriate class and function within that class that implements processing for request based on supplied 'command' within. Call that (async) function and return its result. If target class/function for given request['command'] is not found then RpcError exception is raised.commandz&Endpoint not found for RPC method "%s" paramsNr)tuple_Endpoints__COMMAND_MAPrjoinr") rrequestr/userr3key cls_handler handler_namehandlers rroute_to_endpointzEndpoints.route_to_endpoint4s)$Gnn c'- - -8((79-../ %($5d$;C$@! \++d++\::W11wx0111111111rNct|D]}|drt||}t|td}|At j|st d|jD][}||j|vr8d |||j|||}t|||f|j||<\dS)z{Registers RPC handlers for all functions within a class. Functions should be decorated with @bind('command', ...)._NzMust be a coroutinez1Duplicate handlers for command {} ({}): {} and {}) dir startswithr"r#r&iscoroutinefunctionrAPPLICABLE_USER_TYPESr7formatr )rnameattrr3 user_typemsgs rregister_rpc_handlerszEndpoints.register_rpc_handlersHs HH D DDs## 3%%DdIt44G.t44 ?'(=>>> 6 D D c/ :::K#%-i8A 04449rrWc"eZdZdZejhZdS) RootEndpointsz'Endpoints available only for root user.N)r r rrPrrNrErrrrYrYqs 11%]OrrYc"eZdZdZejhZdS)UserOnlyEndpointsz,Endpoints available only for non root users.N)r r rrPrrMrErrrr[r[ws"66%./rr[cFtjtj|||S)z4Decorator replacing functools.wraps for rpc handlerswrappedassignedupdated) functoolspartialupdate_wrapperr]s rwrapsrds-       rcfd}|S)z4Mark a function as processing RPC calls for command.c4t|t|Sr!)setattrr#)funcr3s r decoratorzbind..decoratorsi))) rr)r3ris` rbindrjs$ r)rar&typingr defence360agent.contracts.configrdefence360agent.utilsr exceptionsrr# Exceptionr rrrWrYr[WRAPPER_ASSIGNMENTSLOOKUP_ASSIGNMENTSWRAPPER_UPDATESrdrjrrrrss555555''''''       I           S.S.S.S.S.S.S.S.l?????i??? ,,,,,I,,, 00000 000 2i\A))2K    rdefence360agent/rpc_tools/__pycache__/middleware.cpython-311.opt-1.pyc0000644000000000000000000003042700000000000022546 0ustar r_j!JddlZddlZddlZddlmZddlmZddlmZddl m Z m Z m Z ddl mZddlmZddlmZdd lmZdd lmZdd lmZejeZd Zeed defdZdZdZ dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dS)N) timedeltawraps)eula)CoreUserType caller_type) LicenseCLN) MessageType)caller_uid_var) hosting_panel) timed_cache) to_threadc<tfd}|S)Nc(Kt|dkr|dn|dtj}t j|} |g|Ri|d{V t j|S#t j|wxYw)Nuser)lengetrROOTr setreset)requestargskwargsrtokenfs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/middleware.pywrapperz(set_caller_type_context..wrappers d))a--tAwwVZZ -N-N%% %74T444V44444444 4  e $ $ $ $K e $ $ $ $s A;;Brrrs` rset_caller_type_contextr!s3 1XX % % % %X % N<)seconds) expirationreturncvKttjd{VS)N) frozensetr HostingPanel get_usersr"r _panel_usersr,#s; =577AACCCCCCCC D DDr"c<tfd}|S)NcXK|d}t|tr|dnd}tjd}|r|r t j|j|krktd{V}|rU||vrQttj d{VD]1}|j |kr$|j|vr|j|d<d|vr |jg|d<n2n3#t$r&}t d||Yd}~nd}~wwxYw |g|Ri|d{VS)Nparamsrusersz,Failed to resolve panel login for uid %s: %s)r isinstancedictr pwdgetpwuidpw_namer,rgetpwallpw_uid Exceptionloggerwarning) rrrr/nameuid panel_userspwers rrz+resolve_caller_panel_login..wrapper,sX&&%/%=%=Gvzz&!!!4 &&  C   <$$,44(4"6"6"6"6"6"6K"&t;'>'>(1#,(?(?"?"?"?"?"?"?&&B!yC//BJ+4M4M13v#*f#4#479zlF7O %   BC Qw0000000000000sBC'' D1DDrr s` rresolve_caller_panel_loginr@(s5 1XX1111X12 Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj|d<|S)N Result should be a dictionary %slicense)r1r2r license_inforrresultrs rrzadd_license..wrapperJssq$)&))))))))&$''  . 7  ' '355y r"rr s` r add_licenserHIs3 1XXX Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj}|d|d|dd|d<|S)NrCstatus license_typeeligible_for_imunify_patch)rKrLrMrD)r1r2r rEr)rrrGrDrs rrz!add_license_user..wrapperYsq$)&))))))))&$''  . 7  ' )++h'#KK77*1++,++  y r"rr s` radd_license_userrNXs3 1XXX" Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|zd}tjrtjst jd{Vsx t jt jt j d}n=#t$r0}dd t|dd}Yd}~nd}~wwxYw||d<|S)NrC)messagetextupdatedzFailed to read EULAzFailed to read EULA: {}r) r1r2r is_validis_freer is_acceptedrQrRrSOSErrorformatstr)rrrG eula_dictr?rs rrzadd_eula..wrapperos6q$)&))))))))&$''  . 7  '   **<*>*> )++++++++  #'<>> $ #'<>>!!II #8 9 @ @Q H H#%!!IIIIII #v s69B00 C*:&C%%C*rr s` radd_eular\ns3 1XXX0 Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj|d<|S)NrCversion)r1r2rVERSIONrFs rrzadd_version..wrapperslq$)&))))))))&$''  . 7  '!Ly r"rr s` r add_versionras3 1XXX Nr"c<tfd}|S)Nc4K|i|d{V\}}||dS)N) max_countitemsr+)rrcountrers rrzmax_count..wrappers?Q///////// u"U333r"rr s` rrdrds3 1XX4444X4 Nr"c<tfd}|S)Nc8K|i|d{V\}}}|||dS)N)rdcountsrer+)rrrdrirers rrzcounts..wrappersD)*D);F););#;#;#;#;#;#; 65&&5IIIr"rr s` rriris8 1XXJJJJXJ Nr"c<tfd}|S)NcKtjdttjd5}|i|d{V}d|D|d<|cdddS#1swxYwYdS)NalwaysT)recordcLg|]!}d|jj"S) )joinrQr).0ws r z5collect_warnings..wrapper..s(!J!J!Jq#((19>":":!J!J!Jr"warnings)rt simplefilterDeprecationWarningcatch_warnings)rrwarnsrGrs rrz!collect_warnings..wrappersh(:;;;  $D 1 1 1 U1d-f--------F!J!JE!J!J!JF:                   sAA#&A#rr s` rcollect_warningsrys3 1XXX Nr"c<tfd}|S)NcZK|i|d{V}t|tsd|i}|S)Nre)r1r2rFs rrz!default_to_items..wrappersNq$)&))))))))&$'' 'v&F r"rr s` rdefault_to_itemsr|s3 1XXX Nr"c<tfd}|S)a This middleware copies 'remote_addr' to 'client_addr'. This is needed because send_command_invoke middleware may remove remote_addr parameter from request. Used for endpoints that need remote_addr in their logic. :param f: :return: clK|dd}||d<|g|Ri|d{VS)Nr/ remote_addr client_addr)r)rrrrrs rrz%preserve_remote_addr..wrappers[h'++M:: !, Qw0000000000000r"rr s` rpreserve_remote_addrrs5 1XX1111X1 Nr"c<tfd}|S)NcKd}|r |d}n d|vr|d}|t|d}d|vr?d}t|dkr |d}n d|vr|d}|tjkrd|d<d|vrd|d<t j|d ||d d }||d{V|dd d|g|Ri|d{VS) Nrsinkr/rrTpasswordz***commandcalling_process)rr/rr)r2rrNON_ROOTr CommandInvokepopprocess_message)rrrrr/ user_typemsgcoros rrz,send_command_invoke_message..wrappersb  "7DD v  &>D  '(+,,FV## t99q== $QIIv%% &vI 111%)F6NV##%*z"+ * ' ,=t D DC&&s++ + + + + + + + H  ! !- 6 6 6T'3D333F333333333r"r)rrs` rsend_command_invoke_messagers4 4[[$4$4$4$4[$4L Nr")*loggingr3rtdatetimer functoolsrdefence360agent.contractsr defence360agent.contracts.configrrr !defence360agent.contracts.licenser "defence360agent.contracts.messagesr defence360agent.rpc_toolsr defence360agent.subsys.panelsr defence360agent.utilsrdefence360agent.utils.threadsr getLogger__name__r9r!r(r,r@rHrNr\rardriryr|rrr+r"rrs ******HHHHHHHHHH888888::::::444444777777------333333  8 $ $      "---...EIEEE/.EB   ,:      *(((((r"defence360agent/rpc_tools/__pycache__/middleware.cpython-311.pyc0000644000000000000000000003042700000000000021607 0ustar r_j!JddlZddlZddlZddlmZddlmZddlmZddl m Z m Z m Z ddl mZddlmZddlmZdd lmZdd lmZdd lmZejeZd Zeed defdZdZdZ dZ!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dS)N) timedeltawraps)eula)CoreUserType caller_type) LicenseCLN) MessageType)caller_uid_var) hosting_panel) timed_cache) to_threadc<tfd}|S)Nc(Kt|dkr|dn|dtj}t j|} |g|Ri|d{V t j|S#t j|wxYw)Nuser)lengetrROOTr setreset)requestargskwargsrtokenfs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/middleware.pywrapperz(set_caller_type_context..wrappers d))a--tAwwVZZ -N-N%% %74T444V44444444 4  e $ $ $ $K e $ $ $ $s A;;Brrrs` rset_caller_type_contextr!s3 1XX % % % %X % N<)seconds) expirationreturncvKttjd{VS)N) frozensetr HostingPanel get_usersr"r _panel_usersr,#s; =577AACCCCCCCC D DDr"c<tfd}|S)NcXK|d}t|tr|dnd}tjd}|r|r t j|j|krktd{V}|rU||vrQttj d{VD]1}|j |kr$|j|vr|j|d<d|vr |jg|d<n2n3#t$r&}t d||Yd}~nd}~wwxYw |g|Ri|d{VS)Nparamsrusersz,Failed to resolve panel login for uid %s: %s)r isinstancedictr pwdgetpwuidpw_namer,rgetpwallpw_uid Exceptionloggerwarning) rrrr/nameuid panel_userspwers rrz+resolve_caller_panel_login..wrapper,sX&&%/%=%=Gvzz&!!!4 &&  C   <$$,44(4"6"6"6"6"6"6K"&t;'>'>(1#,(?(?"?"?"?"?"?"?&&B!yC//BJ+4M4M13v#*f#4#479zlF7O %   BC Qw0000000000000sBC'' D1DDrr s` rresolve_caller_panel_loginr@(s5 1XX1111X12 Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj|d<|S)N Result should be a dictionary %slicense)r1r2r license_inforrresultrs rrzadd_license..wrapperJssq$)&))))))))&$''  . 7  ' '355y r"rr s` r add_licenserHIs3 1XXX Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj}|d|d|dd|d<|S)NrCstatus license_typeeligible_for_imunify_patch)rKrLrMrD)r1r2r rEr)rrrGrDrs rrz!add_license_user..wrapperYsq$)&))))))))&$''  . 7  ' )++h'#KK77*1++,++  y r"rr s` radd_license_userrNXs3 1XXX" Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|zd}tjrtjst jd{Vsx t jt jt j d}n=#t$r0}dd t|dd}Yd}~nd}~wwxYw||d<|S)NrC)messagetextupdatedzFailed to read EULAzFailed to read EULA: {}r) r1r2r is_validis_freer is_acceptedrQrRrSOSErrorformatstr)rrrG eula_dictr?rs rrzadd_eula..wrapperos6q$)&))))))))&$''  . 7  '   **<*>*> )++++++++  #'<>> $ #'<>>!!II #8 9 @ @Q H H#%!!IIIIII #v s69B00 C*:&C%%C*rr s` radd_eular\ns3 1XXX0 Nr"c<tfd}|S)NcK|i|d{V}t|ts Jd|ztj|d<|S)NrCversion)r1r2rVERSIONrFs rrzadd_version..wrapperslq$)&))))))))&$''  . 7  '!Ly r"rr s` r add_versionras3 1XXX Nr"c<tfd}|S)Nc4K|i|d{V\}}||dS)N) max_countitemsr+)rrcountrers rrzmax_count..wrappers?Q///////// u"U333r"rr s` rrdrds3 1XX4444X4 Nr"c<tfd}|S)Nc8K|i|d{V\}}}|||dS)N)rdcountsrer+)rrrdrirers rrzcounts..wrappersD)*D);F););#;#;#;#;#;#; 65&&5IIIr"rr s` rriris8 1XXJJJJXJ Nr"c<tfd}|S)NcKtjdttjd5}|i|d{V}d|D|d<|cdddS#1swxYwYdS)NalwaysT)recordcLg|]!}d|jj"S) )joinrQr).0ws r z5collect_warnings..wrapper..s(!J!J!Jq#((19>":":!J!J!Jr"warnings)rt simplefilterDeprecationWarningcatch_warnings)rrwarnsrGrs rrz!collect_warnings..wrappersh(:;;;  $D 1 1 1 U1d-f--------F!J!JE!J!J!JF:                   sAA#&A#rr s` rcollect_warningsrys3 1XXX Nr"c<tfd}|S)NcZK|i|d{V}t|tsd|i}|S)Nre)r1r2rFs rrz!default_to_items..wrappersNq$)&))))))))&$'' 'v&F r"rr s` rdefault_to_itemsr|s3 1XXX Nr"c<tfd}|S)a This middleware copies 'remote_addr' to 'client_addr'. This is needed because send_command_invoke middleware may remove remote_addr parameter from request. Used for endpoints that need remote_addr in their logic. :param f: :return: clK|dd}||d<|g|Ri|d{VS)Nr/ remote_addr client_addr)r)rrrrrs rrz%preserve_remote_addr..wrappers[h'++M:: !, Qw0000000000000r"rr s` rpreserve_remote_addrrs5 1XX1111X1 Nr"c<tfd}|S)NcKd}|r |d}n d|vr|d}|t|d}d|vr?d}t|dkr |d}n d|vr|d}|tjkrd|d<d|vrd|d<t j|d ||d d }||d{V|dd d|g|Ri|d{VS) Nrsinkr/rrTpasswordz***commandcalling_process)rr/rr)r2rrNON_ROOTr CommandInvokepopprocess_message)rrrrr/ user_typemsgcoros rrz,send_command_invoke_message..wrappersb  "7DD v  &>D  '(+,,FV## t99q== $QIIv%% &vI 111%)F6NV##%*z"+ * ' ,=t D DC&&s++ + + + + + + + H  ! !- 6 6 6T'3D333F333333333r"r)rrs` rsend_command_invoke_messagers4 4[[$4$4$4$4[$4L Nr")*loggingr3rtdatetimer functoolsrdefence360agent.contractsr defence360agent.contracts.configrrr !defence360agent.contracts.licenser "defence360agent.contracts.messagesr defence360agent.rpc_toolsr defence360agent.subsys.panelsr defence360agent.utilsrdefence360agent.utils.threadsr getLogger__name__r9r!r(r,r@rHrNr\rardriryr|rrr+r"rrs ******HHHHHHHHHH888888::::::444444777777------333333  8 $ $      "---...EIEEE/.EB   ,:      *(((((r"defence360agent/rpc_tools/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000001632300000000000021570 0ustar r_j1FddlZddlZddlZddlmZddlmZmZddlm Z ddl m Z ddl m Z mZmZddlZddlZddlmZddlmZdd lmZdd lmZmZmZmZd Zd ZddZ ddee ddffdZ!dde ee fdZ"eddZ#dZ$ ddZ%dS)N)suppress) lru_cachewraps)chain)Path)OptionalTuple Generator) SimpleRpc)run_in_executor)ValidationError) AV_PID_PATHIM360_NON_RESIDENT_PID_PATHIM360_RESIDENT_PID_PATHantivirus_modecVtjrtnt}|rzt j}tt5t| }||kotj |cdddS#1swxYwYdS)z/Check if non-resident agent instance is runningNF) renabledrrexistsosgetpidr Exceptionint read_textpsutil pid_exists)rpc_process_pid_path current_pidpids T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/utils.pyrpc_is_runningr s &-N 3N""$$Aikk i  A A*446677C+%@&*;C*@*@ A A A A A A A A A A A A A A A A 5s;BB"%B"ctjrtStjrMt j}ttj}||kotj |SdS)z&Check if the agent instance is runningF) ConfigSOCKET_ACTIVATIONr rrrrrrrr)rrs r is_runningr$'sl  %''=ikk )35566k!resultr5s rget_schema_pathsrHGsi$ ,T%[[^^"F >> ', 6D MM  K. . 6  Mr%cg}t|D]$}|t|%tt |SN)rHappendr:dictr)r>r& base_paths rprepare_schemarOSsM F%e,,//  l9--.... v  r%c<tfd}|S)NcdKttjfdd{VS)NciSrK)argsfkwargssrz.wrapper.._saa.@.@.@r%)r asyncioget_event_loop)rTrVrUs``rwrapperz*run_in_executor_decorator..wrapper\sW$  " $ $&@&@&@&@&@&@         r%)r)rUrZs` rrun_in_executor_decoratorr[[s3 1XX    X Nr%cg}|D]w}|d|d|} } | r,|r*||| | N||| |xt|} t|} | dkr/| | kr)||| | |rt |iS)aP :param list affected: IPs that were changed during operation :param list of tuples || list of str not_affected: IPs & it's listnames that weren't changed during operation :param list all_list: list of all IPs that take place in operation :param str success_warning: msg if IP was changed :param str failure_warning: msg if IPs wasn't changed and it's absent in any other lists :param str in_another_list_warning: msg if IPs wasn't changed , however it present in another list :return list of st warnings: msg to be printed reclistnamerI)getrLformatlenr ) affected not_affected dest_listnameall_listsuccess_warningfailure_warningin_another_list_warningwarningsitemrecordr^ num_deleted total_nums rgenerate_warningsrnes*HKK;](K(K  K/ K OO3::68LL M M M M OOO226=II J J J Jh--KH I1}}k11..{IFFGGG(h''' Ir%)Nr&rK)&r+rXr contextlibr functoolsrr itertoolsrpathlibrtypingrr r r.r defence360agent.contracts.configr r"$defence360agent.model.simplificationr "defence360agent.rpc_tools.validater defence360agent.utilsrrrrr r$r:r)rHrOr[rnrSr%rrxs  &&&&&&&&---------- @@@@@@@@@@@@>>>>>>       ) ) ) )#tT4   HU4[1     1   "!&&&&&&r%defence360agent/rpc_tools/__pycache__/utils.cpython-311.pyc0000644000000000000000000001632300000000000020631 0ustar r_j1FddlZddlZddlZddlmZddlmZmZddlm Z ddl m Z ddl m Z mZmZddlZddlZddlmZddlmZdd lmZdd lmZmZmZmZd Zd ZddZ ddee ddffdZ!dde ee fdZ"eddZ#dZ$ ddZ%dS)N)suppress) lru_cachewraps)chain)Path)OptionalTuple Generator) SimpleRpc)run_in_executor)ValidationError) AV_PID_PATHIM360_NON_RESIDENT_PID_PATHIM360_RESIDENT_PID_PATHantivirus_modecVtjrtnt}|rzt j}tt5t| }||kotj |cdddS#1swxYwYdS)z/Check if non-resident agent instance is runningNF) renabledrrexistsosgetpidr Exceptionint read_textpsutil pid_exists)rpc_process_pid_path current_pidpids T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/utils.pyrpc_is_runningr s &-N 3N""$$Aikk i  A A*446677C+%@&*;C*@*@ A A A A A A A A A A A A A A A A 5s;BB"%B"ctjrtStjrMt j}ttj}||kotj |SdS)z&Check if the agent instance is runningF) ConfigSOCKET_ACTIVATIONr rrrrrrrr)rrs r is_runningr$'sl  %''=ikk )35566k!resultr5s rget_schema_pathsrHGsi$ ,T%[[^^"F >> ', 6D MM  K. . 6  Mr%cg}t|D]$}|t|%tt |SN)rHappendr:dictr)r>r& base_paths rprepare_schemarOSsM F%e,,//  l9--.... v  r%c<tfd}|S)NcdKttjfdd{VS)NciSrK)argsfkwargssrz.wrapper.._saa.@.@.@r%)r asyncioget_event_loop)rTrVrUs``rwrapperz*run_in_executor_decorator..wrapper\sW$  " $ $&@&@&@&@&@&@         r%)r)rUrZs` rrun_in_executor_decoratorr[[s3 1XX    X Nr%cg}|D]w}|d|d|} } | r,|r*||| | N||| |xt|} t|} | dkr/| | kr)||| | |rt |iS)aP :param list affected: IPs that were changed during operation :param list of tuples || list of str not_affected: IPs & it's listnames that weren't changed during operation :param list all_list: list of all IPs that take place in operation :param str success_warning: msg if IP was changed :param str failure_warning: msg if IPs wasn't changed and it's absent in any other lists :param str in_another_list_warning: msg if IPs wasn't changed , however it present in another list :return list of st warnings: msg to be printed reclistnamerI)getrLformatlenr ) affected not_affected dest_listnameall_listsuccess_warningfailure_warningin_another_list_warningwarningsitemrecordr^ num_deleted total_nums rgenerate_warningsrnes*HKK;](K(K  K/ K OO3::68LL M M M M OOO226=II J J J Jh--KH I1}}k11..{IFFGGG(h''' Ir%)Nr&rK)&r+rXr contextlibr functoolsrr itertoolsrpathlibrtypingrr r r.r defence360agent.contracts.configr r"$defence360agent.model.simplificationr "defence360agent.rpc_tools.validater defence360agent.utilsrrrrr r$r:r)rHrOr[rnrSr%rrxs  &&&&&&&&---------- @@@@@@@@@@@@>>>>>>       ) ) ) )#tT4   HU4[1     1   "!&&&&&&r%defence360agent/rpc_tools/__pycache__/validate.cpython-311.opt-1.pyc0000644000000000000000000003145000000000000022217 0ustar r_j'DddlZddlZddlZddlZddlZddlmZddlmZddl m Z ddl m Z m Z mZddlmZddlmZmZejeZejdZGd d eZed d d gZGddeZGdde ZdZdZdZ dS)N) namedtuplewraps) Validator)ANTIVIRUS_MODE BackupRestoreMalware) LicenseCLN) BackupSystem get_backendz^[A-Fa-f0-9]{64}$ceZdZddZdS)ValidationErrorNcbt|tr |g|_n||_|pi|_dSN) isinstancestrerrors extra_data)selfrrs W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/validate.py__init__zValidationError.__init__s6 fc " " !!(DKK DK$*r)__name__ __module__ __qualname__rrrrrs(++++++rr OrderByBase column_namedescc4eZdZfdZedZxZS)OrderBycJt|||Sr)super__new__)clsrr __class__s rr$zOrderBy.__new__$swwsK666rc  tjd|dd\}}|||dkS#t$r5}tdt ||d}~wwxYw)zP :param ob_string: for example: 'user+', 'id-' :return: z ^(.+)([+|-])-zIncorrect order_by: ({}): {}N)recompilesplit ValueErrorformatr)r% ob_stringcol_namesignes r fromstringzOrderBy.fromstring's  Z77==iHH2NNHd3x-- -   .55c!ffiHH  sAA B0A>>B)rrrr$ classmethodr4 __classcell__r&s@rr!r!#sS77777  [     rr!ceZdZdZfdZdZdZdZdZde fdZ d Z d Z d Z d efd ZdZdZdZdZdZdZdZde fdZdZdZde ded dfdZxZS)SchemaValidatorz%Y-%m-%dcHtj|i|i|_dSr)r#rr)rargskwargsr&s rrzSchemaValidator.__init__9s*$)&)))rcdt|tr|St|Sr)rr!r4rvalues r_normalize_coerce_order_byz*SchemaValidator._normalize_coerce_order_by=s- eW % % L!!%(((rcht|Sr)rstriplowerr>s r_normalize_coerce_sha256hashz,SchemaValidator._normalize_coerce_sha256hashBs&5zz!!'')))rc4trdS| tjS|S)NF)rr DATABASE_SCAN_ENABLEDr>s r_normalize_coerce_scan_dbz)SchemaValidator._normalize_coerce_scan_dbEs"  5 =0 0 rc4t|trdSdS)NTF)rr!r>s r_validate_type_order_byz'SchemaValidator._validate_type_order_byLs eW % % 4urr?cttt|Sr) SHA256_REGEXPmatchrrBr>s r_validate_type_sha256hashz)SchemaValidator._validate_type_sha256hashQs(""3u::#3#3#5#5666rc|rJtj|s-||d|dSdSdS)z#{'type': 'boolean', 'empty': False}zPath {} should be absoluteN)ospathisabs_errorr/)ris_absolute_pathfieldr?s r_validate_is_absolute_pathz*SchemaValidator._validate_is_absolute_pathTsc  O7=='' O E#?#F#Fu#M#MNNNNN O O O Orc|r? |ddS#t$r||dYdSwxYwdS)z{'type': 'boolean'}asciizMust only contain ascii symbolsN)encodeUnicodeEncodeErrorrR)risasciirTr?s r_validate_isasciiz!SchemaValidator._validate_isasciiZsr  F F W%%%%%% F F F E#DEEEEEE F F Fs  ??c t|Sr)intr>s r_normalize_coerce_intz%SchemaValidator._normalize_coerce_intbs5zzrreturnctjtjSr)mathceildatetimenow timestamp)rdocuments r_normalize_default_setter_nowz-SchemaValidator._normalize_default_setter_nowes-y*..00::<<===rcdS)a;{'type': 'dict', 'empty': False, 'schema': { 'users': {'type': 'list', 'allowed': ['non-root', 'root'], 'empty': False}, 'require_rpc': {'type': 'string', 'empty': True, 'default': 'running', 'allowed': ['running', 'stopped', 'any', 'direct']} }} Nrrr;r<s r _validate_clizSchemaValidator._validate_cliircdS)z"{'type': 'string', 'empty': False}Nrris r_validate_helpzSchemaValidator._validate_helpsrkrcdS)z4{'type': 'boolean', 'empty': True, 'default': False}Nrris r_validate_positionalz$SchemaValidator._validate_positionalwrkrcdS)z!{'type': 'string', 'empty': True}Nrris r_validate_return_typez%SchemaValidator._validate_return_type{rkrcdS)z5{'type': 'boolean', 'empty': False, 'default': False}Nrris r_validate_cli_onlyz"SchemaValidator._validate_cli_only~rkrcdS)z Parameter can be passed via the specified environment variable. The value specified via a CLI argument takes precedence. The rule's arguments are validated against this schema: {'type': 'string', 'empty': False} Nrris r_validate_envvarz SchemaValidator._validate_envvarrkrcdS)a Parameter will only be accepted if provided via environment variable specified by `envvar`. It will be rejected if passed as a CLI argument. The rule's arguments are validated against this schema: {'type': 'boolean', 'default': False} Nrris r_validate_envvar_onlyz%SchemaValidator._validate_envvar_onlyrkrcH|rtj|S|Sr)rOrPabspathr>s r_normalize_coerce_pathz&SchemaValidator._normalize_coerce_paths#  *7??5)) ) rcNt|tr|St|Sr)rr r r>s r_normalize_coerce_backup_systemz/SchemaValidator._normalize_coerce_backup_systems' e\ * * L5!!!rcttjrtjs||ddSdS)NzBackup is not enabled!)rENABLED backup_systemrR)rrTr?s r_validator_backup_is_enabledz,SchemaValidator._validator_backup_is_enabledsB% 9-*E*G*G 9 KK7 8 8 8 8 8 9 9rrTNc  tj|dS#t$r5}||d|dt |dYd}~dSd}~wwxYw)NzIncorrect timestamp: z ())rc fromtimestampr.rRr)rrTr?r3s r_validator_timestampz$SchemaValidator._validator_timestamps K   + +E 2 2 2 2 2 K K K KKIuIIAIII J J J J J J J J J Ks# A"*AA")rrr _DATE_FORMATrr@rDrGrIrrMrUr[r^r]rgrjrmrorqrsrurwrzr|rrr6r7s@rr9r96sL))) *** 7s7777OOO FFF>>>>>   111CCC000DDD      C """ 999K#KcKdKKKKKKKKrr9c$|||id}||||isNtd|||jt |j|j|j|S)NT)always_return_documentz6Validation error with command {}, params {}, errors {}) normalizedvalidateloggerwarningr/rrrrf) validatorhashableparamsvaluess rrrs  ! ! 64"F   x)9: ; ;F D K K&)"2     i. 0DEEE  h ''rcfd}|S)Nc@tfd}|S)NcKt|d}t||d|d<|g|Ri|d{V}|S)Ncommandr)tupler)requestr;r<rresultfrs rwrapperz5validate_middleware..wrapped..wrappersqWY/00H (8WX%6!!GH 1W6t666v66666666FMrr)rrrs` rwrappedz$validate_middleware..wrappeds9 q       rr)rrs` rvalidate_middlewarers#      Nrcptdtfd}tr|SS)zz Decorator for CLI commands methods that ensures that the AV+ license is valid. :raises ValidationError: zImunifyAV+ license requiredcNKtjr|i|d{VSr)r is_valid_av_plus)r;r< exceptionfuncs r async_wrapperz/validate_av_plus_license..async_wrappersD  & ( ( /t.v........ .r)rrr)rrrs` @rvalidate_av_plus_licensersW =>>I 4[[[  Kr)!rcloggingrarOr+ collectionsr functoolsrcerberus.validatorr defence360agent.contracts.configrrr !defence360agent.contracts.licenser %defence360agent.subsys.backup_systemsr r getLoggerrrr,rK Exceptionrrr!r9rrrrrrrs """"""(((((( 988888KKKKKKKK  8 $ $ .// +++++i+++j(?@@ k&rKrKrKrKrKirKrKrKj ( ( (    rdefence360agent/rpc_tools/__pycache__/validate.cpython-311.pyc0000644000000000000000000003145000000000000021260 0ustar r_j'DddlZddlZddlZddlZddlZddlmZddlmZddl m Z ddl m Z m Z mZddlmZddlmZmZejeZejdZGd d eZed d d gZGddeZGdde ZdZdZdZ dS)N) namedtuplewraps) Validator)ANTIVIRUS_MODE BackupRestoreMalware) LicenseCLN) BackupSystem get_backendz^[A-Fa-f0-9]{64}$ceZdZddZdS)ValidationErrorNcbt|tr |g|_n||_|pi|_dSN) isinstancestrerrors extra_data)selfrrs W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/rpc_tools/validate.py__init__zValidationError.__init__s6 fc " " !!(DKK DK$*r)__name__ __module__ __qualname__rrrrrs(++++++rr OrderByBase column_namedescc4eZdZfdZedZxZS)OrderBycJt|||Sr)super__new__)clsrr __class__s rr$zOrderBy.__new__$swwsK666rc  tjd|dd\}}|||dkS#t$r5}tdt ||d}~wwxYw)zP :param ob_string: for example: 'user+', 'id-' :return: z ^(.+)([+|-])-zIncorrect order_by: ({}): {}N)recompilesplit ValueErrorformatr)r% ob_stringcol_namesignes r fromstringzOrderBy.fromstring's  Z77==iHH2NNHd3x-- -   .55c!ffiHH  sAA B0A>>B)rrrr$ classmethodr4 __classcell__r&s@rr!r!#sS77777  [     rr!ceZdZdZfdZdZdZdZdZde fdZ d Z d Z d Z d efd ZdZdZdZdZdZdZdZde fdZdZdZde ded dfdZxZS)SchemaValidatorz%Y-%m-%dcHtj|i|i|_dSr)r#rr)rargskwargsr&s rrzSchemaValidator.__init__9s*$)&)))rcdt|tr|St|Sr)rr!r4rvalues r_normalize_coerce_order_byz*SchemaValidator._normalize_coerce_order_by=s- eW % % L!!%(((rcht|Sr)rstriplowerr>s r_normalize_coerce_sha256hashz,SchemaValidator._normalize_coerce_sha256hashBs&5zz!!'')))rc4trdS| tjS|S)NF)rr DATABASE_SCAN_ENABLEDr>s r_normalize_coerce_scan_dbz)SchemaValidator._normalize_coerce_scan_dbEs"  5 =0 0 rc4t|trdSdS)NTF)rr!r>s r_validate_type_order_byz'SchemaValidator._validate_type_order_byLs eW % % 4urr?cttt|Sr) SHA256_REGEXPmatchrrBr>s r_validate_type_sha256hashz)SchemaValidator._validate_type_sha256hashQs(""3u::#3#3#5#5666rc|rJtj|s-||d|dSdSdS)z#{'type': 'boolean', 'empty': False}zPath {} should be absoluteN)ospathisabs_errorr/)ris_absolute_pathfieldr?s r_validate_is_absolute_pathz*SchemaValidator._validate_is_absolute_pathTsc  O7=='' O E#?#F#Fu#M#MNNNNN O O O Orc|r? |ddS#t$r||dYdSwxYwdS)z{'type': 'boolean'}asciizMust only contain ascii symbolsN)encodeUnicodeEncodeErrorrR)risasciirTr?s r_validate_isasciiz!SchemaValidator._validate_isasciiZsr  F F W%%%%%% F F F E#DEEEEEE F F Fs  ??c t|Sr)intr>s r_normalize_coerce_intz%SchemaValidator._normalize_coerce_intbs5zzrreturnctjtjSr)mathceildatetimenow timestamp)rdocuments r_normalize_default_setter_nowz-SchemaValidator._normalize_default_setter_nowes-y*..00::<<===rcdS)a;{'type': 'dict', 'empty': False, 'schema': { 'users': {'type': 'list', 'allowed': ['non-root', 'root'], 'empty': False}, 'require_rpc': {'type': 'string', 'empty': True, 'default': 'running', 'allowed': ['running', 'stopped', 'any', 'direct']} }} Nrrr;r<s r _validate_clizSchemaValidator._validate_cliircdS)z"{'type': 'string', 'empty': False}Nrris r_validate_helpzSchemaValidator._validate_helpsrkrcdS)z4{'type': 'boolean', 'empty': True, 'default': False}Nrris r_validate_positionalz$SchemaValidator._validate_positionalwrkrcdS)z!{'type': 'string', 'empty': True}Nrris r_validate_return_typez%SchemaValidator._validate_return_type{rkrcdS)z5{'type': 'boolean', 'empty': False, 'default': False}Nrris r_validate_cli_onlyz"SchemaValidator._validate_cli_only~rkrcdS)z Parameter can be passed via the specified environment variable. The value specified via a CLI argument takes precedence. The rule's arguments are validated against this schema: {'type': 'string', 'empty': False} Nrris r_validate_envvarz SchemaValidator._validate_envvarrkrcdS)a Parameter will only be accepted if provided via environment variable specified by `envvar`. It will be rejected if passed as a CLI argument. The rule's arguments are validated against this schema: {'type': 'boolean', 'default': False} Nrris r_validate_envvar_onlyz%SchemaValidator._validate_envvar_onlyrkrcH|rtj|S|Sr)rOrPabspathr>s r_normalize_coerce_pathz&SchemaValidator._normalize_coerce_paths#  *7??5)) ) rcNt|tr|St|Sr)rr r r>s r_normalize_coerce_backup_systemz/SchemaValidator._normalize_coerce_backup_systems' e\ * * L5!!!rcttjrtjs||ddSdS)NzBackup is not enabled!)rENABLED backup_systemrR)rrTr?s r_validator_backup_is_enabledz,SchemaValidator._validator_backup_is_enabledsB% 9-*E*G*G 9 KK7 8 8 8 8 8 9 9rrTNc  tj|dS#t$r5}||d|dt |dYd}~dSd}~wwxYw)NzIncorrect timestamp: z ())rc fromtimestampr.rRr)rrTr?r3s r_validator_timestampz$SchemaValidator._validator_timestamps K   + +E 2 2 2 2 2 K K K KKIuIIAIII J J J J J J J J J Ks# A"*AA")rrr _DATE_FORMATrr@rDrGrIrrMrUr[r^r]rgrjrmrorqrsrurwrzr|rrr6r7s@rr9r96sL))) *** 7s7777OOO FFF>>>>>   111CCC000DDD      C """ 999K#KcKdKKKKKKKKrr9c$|||id}||||isNtd|||jt |j|j|j|S)NT)always_return_documentz6Validation error with command {}, params {}, errors {}) normalizedvalidateloggerwarningr/rrrrf) validatorhashableparamsvaluess rrrs  ! ! 64"F   x)9: ; ;F D K K&)"2     i. 0DEEE  h ''rcfd}|S)Nc@tfd}|S)NcKt|d}t||d|d<|g|Ri|d{V}|S)Ncommandr)tupler)requestr;r<rresultfrs rwrapperz5validate_middleware..wrapped..wrappersqWY/00H (8WX%6!!GH 1W6t666v66666666FMrr)rrrs` rwrappedz$validate_middleware..wrappeds9 q       rr)rrs` rvalidate_middlewarers#      Nrcptdtfd}tr|SS)zz Decorator for CLI commands methods that ensures that the AV+ license is valid. :raises ValidationError: zImunifyAV+ license requiredcNKtjr|i|d{VSr)r is_valid_av_plus)r;r< exceptionfuncs r async_wrapperz/validate_av_plus_license..async_wrappersD  & ( ( /t.v........ .r)rrr)rrrs` @rvalidate_av_plus_licensersW =>>I 4[[[  Kr)!rcloggingrarOr+ collectionsr functoolsrcerberus.validatorr defence360agent.contracts.configrrr !defence360agent.contracts.licenser %defence360agent.subsys.backup_systemsr r getLoggerrrr,rK Exceptionrrr!r9rrrrrrrs """"""(((((( 988888KKKKKKKK  8 $ $ .// +++++i+++j(?@@ k&rKrKrKrKrKirKrKrKj ( ( (    rdefence360agent/rpc_tools/exceptions.py0000644000000000000000000000062300000000000015306 0ustar from defence360agent.contracts import config class RpcError(RuntimeError): pass class ResponseError(RpcError): pass class SocketError(RpcError): pass class ServiceStateError(SocketError): def __init__(self, state="stopped"): super().__init__( "{} service is {}.".format(config.Core.PRODUCT, state) ) class NonRootValidationError(RpcError): pass defence360agent/rpc_tools/lookup.py0000644000000000000000000001061500000000000014440 0ustar import functools import inspect from typing import Any from defence360agent.contracts.config import UserType from defence360agent.utils import Scope from .exceptions import RpcError _RPC_MARK = "__rpc_command" class DuplicateHandlerError(Exception): pass class NotCoroutineError(Exception): pass class Endpoints: """Endpoints class implements registration and lookup for functions implementing RPC calls.""" SCOPE = Scope.AV_IM360 APPLICABLE_USER_TYPES = set() # type: Set[str] __COMMAND_MAP = { UserType.ROOT: {}, UserType.NON_ROOT: {}, } # type: Dict[str, Dict] _subclasses = [] def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls._subclasses.append(cls) @classmethod def get_active_endpoints(cls): # consider endpoint as active if it has at least one RPC call handler active_endpoints = [] for subcls in cls._subclasses: rpc_handlers = inspect.getmembers( subcls, lambda item: getattr(item, _RPC_MARK, None) ) if rpc_handlers: active_endpoints.append(subcls) return active_endpoints def __init__(self, sink): self._sink = sink @classmethod async def route_to_endpoint(cls, request, sink, user=UserType.ROOT) -> Any: """Find appropriate class and function within that class that implements processing for request based on supplied 'command' within. Call that (async) function and return its result. If target class/function for given request['command'] is not found then RpcError exception is raised.""" command = request["command"] key = tuple(command) if key not in cls.__COMMAND_MAP[user]: raise RpcError( 'Endpoint not found for RPC method "%s"' % " ".join(request["command"]) ) cls_handler, handler_name = cls.__COMMAND_MAP[user][key] handler = getattr(cls_handler(sink), handler_name) return await handler(**request["params"]) @classmethod def register_rpc_handlers(cls) -> None: """Registers RPC handlers for all functions within a class. Functions should be decorated with @bind('command', ...).""" for name in dir(cls): if name.startswith("_"): continue attr = getattr(cls, name) command = getattr(attr, _RPC_MARK, None) if command is None: continue if not inspect.iscoroutinefunction(attr): raise NotCoroutineError("Must be a coroutine") for user_type in cls.APPLICABLE_USER_TYPES: if command in cls.__COMMAND_MAP[user_type]: msg = ( "Duplicate handlers for command {} ({}): {} and {}" .format( command, user_type, cls.__COMMAND_MAP[user_type][command], attr, ) ) raise DuplicateHandlerError(msg) cls.__COMMAND_MAP[user_type][command] = (cls, name) @classmethod def reset_rpc_handlers(cls): """Clears all previously made registrations.""" for user_type in {UserType.NON_ROOT, UserType.ROOT}: cls.__COMMAND_MAP[user_type] = {} class CommonEndpoints(Endpoints): """Endpoints available both for root and non root users.""" APPLICABLE_USER_TYPES = {UserType.NON_ROOT, UserType.ROOT} class RootEndpoints(Endpoints): """Endpoints available only for root user.""" APPLICABLE_USER_TYPES = {UserType.ROOT} class UserOnlyEndpoints(Endpoints): """Endpoints available only for non root users.""" APPLICABLE_USER_TYPES = {UserType.NON_ROOT} LOOKUP_ASSIGNMENTS = functools.WRAPPER_ASSIGNMENTS + (_RPC_MARK,) def wraps( wrapped, assigned=LOOKUP_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES ): """Decorator replacing functools.wraps for rpc handlers""" return functools.partial( functools.update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated, ) def bind(*command): """Mark a function as processing RPC calls for command.""" def decorator(func): setattr(func, _RPC_MARK, command) return func return decorator defence360agent/rpc_tools/middleware.py0000644000000000000000000002070600000000000015246 0ustar import logging import pwd import warnings from datetime import timedelta from functools import wraps from defence360agent.contracts import eula from defence360agent.contracts.config import Core, UserType, caller_type from defence360agent.contracts.license import LicenseCLN from defence360agent.contracts.messages import MessageType from defence360agent.rpc_tools import caller_uid_var from defence360agent.subsys.panels import hosting_panel from defence360agent.utils import timed_cache from defence360agent.utils.threads import to_thread logger = logging.getLogger(__name__) def set_caller_type_context(f): @wraps(f) async def wrapper(request, *args, **kwargs): # Match how send_command_invoke_message extracts the caller: the # positional user from the RPC dispatch (cb(request, sink, user)), # else kwargs, else ROOT for the direct-CLI path (cb(request, sink)). user = args[1] if len(args) > 1 else kwargs.get("user", UserType.ROOT) token = caller_type.set(user) try: return await f(request, *args, **kwargs) finally: caller_type.reset(token) return wrapper @timed_cache(expiration=timedelta(seconds=60)) async def _panel_users() -> frozenset: return frozenset(await hosting_panel.HostingPanel().get_users()) def resolve_caller_panel_login(f): # Plesk additional web/FTP users share the subscription sysuser's UID, # so the getpwuid()-derived caller name may be a non-panel entry; prefer # the same-UID panel login, matching scan-side owner attribution. @wraps(f) async def wrapper(request, *args, **kwargs): params = request.get("params") name = params.get("user") if isinstance(params, dict) else None uid = caller_uid_var.get(None) if name and uid: try: # only re-resolve names that came from getpwuid(); a name # that differs was authenticated another way (e.g. a PAM # user in a generic-panel JWT, with the UI process running # under an unrelated UID) and must be kept as is if pwd.getpwuid(uid).pw_name == name: panel_users = await _panel_users() if panel_users and name not in panel_users: for pw in await to_thread(pwd.getpwall): if pw.pw_uid == uid and pw.pw_name in panel_users: params["user"] = pw.pw_name if "users" in params: params["users"] = [pw.pw_name] break except Exception as e: logger.warning( "Failed to resolve panel login for uid %s: %s", uid, e ) return await f(request, *args, **kwargs) return wrapper def add_license(f): @wraps(f) async def wrapper(*args, **kwargs): result = await f(*args, **kwargs) assert isinstance(result, dict), ( "Result should be a dictionary %s" % result ) # license_info() includes eligible_for_imunify_patch for schema compatibility # see https://gerrit.cloudlinux.com/c/defence360/+/195229/comment/c1b1c514_1462b41c/ result["license"] = LicenseCLN.license_info() return result return wrapper def add_license_user(f): @wraps(f) async def wrapper(*args, **kwargs): result = await f(*args, **kwargs) assert isinstance(result, dict), ( "Result should be a dictionary %s" % result ) # license_info() includes eligible_for_imunify_patch for schema compatibility # see https://gerrit.cloudlinux.com/c/defence360/+/195229/comment/c1b1c514_1462b41c/ license = LicenseCLN.license_info() result["license"] = { "status": license["status"], "license_type": license.get("license_type"), "eligible_for_imunify_patch": license.get( "eligible_for_imunify_patch" ), } return result return wrapper def add_eula(f): @wraps(f) async def wrapper(*args, **kwargs): result = await f(*args, **kwargs) assert isinstance(result, dict), ( "Result should be a dictionary %s" % result ) eula_dict = None # do not show eula if not registered or using free AV version if LicenseCLN.is_valid() and (not LicenseCLN.is_free()): if not await eula.is_accepted(): try: eula_dict = { "message": eula.message(), "text": eula.text(), "updated": eula.updated(), } except OSError as e: eula_dict = { "message": "Failed to read EULA", "text": "Failed to read EULA: {}".format(str(e)), "updated": "", } result["eula"] = eula_dict return result return wrapper def add_version(f): @wraps(f) async def wrapper(*args, **kwargs): result = await f(*args, **kwargs) assert isinstance(result, dict), ( "Result should be a dictionary %s" % result ) result["version"] = Core.VERSION return result return wrapper def max_count(f): @wraps(f) async def wrapper(*args, **kwargs): count, items = await f(*args, **kwargs) return {"max_count": count, "items": items} return wrapper def counts(f): @wraps(f) async def wrapper(*args, **kwargs): max_count, counts, items = await f(*args, **kwargs) return {"max_count": max_count, "counts": counts, "items": items} return wrapper def collect_warnings(f): @wraps(f) async def wrapper(*args, **kwargs): warnings.simplefilter("always", DeprecationWarning) with warnings.catch_warnings(record=True) as warns: result = await f(*args, **kwargs) result["warnings"] = [" ".join(w.message.args) for w in warns] return result return wrapper # Need only for backward compatibility def default_to_items(f): @wraps(f) async def wrapper(*args, **kwargs): result = await f(*args, **kwargs) if not isinstance(result, dict): result = {"items": result} return result return wrapper def preserve_remote_addr(f): """ This middleware copies 'remote_addr' to 'client_addr'. This is needed because send_command_invoke middleware may remove remote_addr parameter from request. Used for endpoints that need remote_addr in their logic. :param f: :return: """ @wraps(f) async def wrapper(request, *args, **kwargs): remote_addr = request["params"].get("remote_addr") request["client_addr"] = remote_addr return await f(request, *args, **kwargs) return wrapper def send_command_invoke_message(coro): @wraps(coro) async def wrapper(request, *args, **kwargs): # get the sink to send CommandInvoke message sink = None if args: sink = args[0] elif "sink" in kwargs: sink = kwargs["sink"] if sink is not None: params = dict(request["params"]) if "user" not in params: # find user type (root/non-root) to determine access rights user_type = None if len(args) > 1: user_type = args[1] elif "user" in kwargs: user_type = kwargs["user"] if user_type == UserType.NON_ROOT: params["user"] = True # don't send passwords if "password" in params: params["password"] = "***" msg = MessageType.CommandInvoke( command=request["command"], params=params, calling_process=request.pop("calling_process", None), ) # MQTT tracing enrichment lives at the # SendToServerClient.send_to_server chokepoint and is gated by # the server-driven mqtt_tracked_methods list, so adding or # removing tracked types is server-side config without an # agent rollout. CommandInvoke is no longer enriched here. await sink.process_message(msg) request["params"].pop("remote_addr", None) return await coro(request, *args, **kwargs) return wrapper defence360agent/rpc_tools/utils.py0000644000000000000000000001006100000000000014262 0ustar import pickle import asyncio import os from contextlib import suppress from functools import lru_cache, wraps from itertools import chain from pathlib import Path from typing import Optional, Tuple, Generator import yaml import psutil from defence360agent.contracts.config import SimpleRpc as Config from defence360agent.model.simplification import run_in_executor from defence360agent.rpc_tools.validate import ValidationError from defence360agent.utils import ( AV_PID_PATH, IM360_NON_RESIDENT_PID_PATH, IM360_RESIDENT_PID_PATH, antivirus_mode, ) def rpc_is_running(): """Check if non-resident agent instance is running""" # we use socket activation, so we could not use socket for this purpose # check process instead rpc_process_pid_path = ( AV_PID_PATH if antivirus_mode.enabled else IM360_NON_RESIDENT_PID_PATH ) if rpc_process_pid_path.exists(): current_pid = os.getpid() with suppress(Exception): pid = int(rpc_process_pid_path.read_text()) return pid != current_pid and psutil.pid_exists(pid) return False def is_running(): """Check if the agent instance is running""" if Config.SOCKET_ACTIVATION: return rpc_is_running() if IM360_RESIDENT_PID_PATH.exists(): current_pid = os.getpid() pid = int(IM360_RESIDENT_PID_PATH.read_text()) return pid != current_pid and psutil.pid_exists(pid) return False def _find_schema(base=None, schema_dir="schema"): for path in find_schema_files(base, schema_dir): if (p := path.with_suffix(".pickle")).exists(): document = pickle.loads(p.read_bytes()) else: document = yaml.safe_load(path.read_text()) for k, v in document.items(): # converting keys - from strings to tuples yield tuple(k.split(" ")), v def find_schema_files( base=None, schema_dir="schema" ) -> Generator[Path, None, None]: p = base / schema_dir for path in [*p.rglob("*.yaml"), *p.rglob("*.yml")]: yield path def get_schema_paths(paths: Optional[Tuple[Path]] = None): result = list(paths)[:] if paths else [] path = Path(__file__).parent.parent / "simple_rpc" result.extend( [ path, path.parent / "feature_management" / "rpc", ] ) return result @lru_cache(1) def prepare_schema(paths): schema = [] for base_path in get_schema_paths(paths): schema.append(_find_schema(base_path)) return dict(chain(*schema)) def run_in_executor_decorator(f): @wraps(f) async def wrapper(*args, **kwargs): return await run_in_executor( asyncio.get_event_loop(), lambda: f(*args, **kwargs) ) return wrapper def generate_warnings( affected, not_affected, dest_listname, all_list, success_warning, failure_warning, in_another_list_warning=None, ): """ :param list affected: IPs that were changed during operation :param list of tuples || list of str not_affected: IPs & it's listnames that weren't changed during operation :param list all_list: list of all IPs that take place in operation :param str success_warning: msg if IP was changed :param str failure_warning: msg if IPs wasn't changed and it's absent in any other lists :param str in_another_list_warning: msg if IPs wasn't changed , however it present in another list :return list of st warnings: msg to be printed """ warnings = [] for item in not_affected: record, listname = item["rec"], item.get("listname", dest_listname) if listname and in_another_list_warning: warnings.append(in_another_list_warning.format(record, listname)) else: warnings.append(failure_warning.format(record, dest_listname)) num_deleted = len(affected) total_num = len(all_list) if total_num > 1 and total_num != num_deleted: warnings.append(success_warning.format(num_deleted, total_num)) if warnings: raise ValidationError(warnings) return {} defence360agent/rpc_tools/validate.py0000644000000000000000000001504700000000000014724 0ustar import datetime import logging import math import os import re from collections import namedtuple from functools import wraps from cerberus.validator import Validator from defence360agent.contracts.config import ( ANTIVIRUS_MODE, BackupRestore, Malware, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.subsys.backup_systems import BackupSystem, get_backend logger = logging.getLogger(__name__) SHA256_REGEXP = re.compile("^[A-Fa-f0-9]{64}$") class ValidationError(Exception): def __init__(self, errors, extra_data=None): if isinstance(errors, str): self.errors = [errors] else: self.errors = errors self.extra_data = extra_data or {} OrderByBase = namedtuple("OrderByBase", ["column_name", "desc"]) class OrderBy(OrderByBase): def __new__(cls, column_name, desc): return super().__new__(cls, column_name, desc) @classmethod def fromstring(cls, ob_string): """ :param ob_string: for example: 'user+', 'id-' :return: """ try: col_name, sign = re.compile("^(.+)([+|-])").split(ob_string)[1:-1] return cls(col_name, sign == "-") except ValueError as e: raise ValueError( "Incorrect order_by: ({}): {}".format(str(e), ob_string) ) class SchemaValidator(Validator): _DATE_FORMAT = "%Y-%m-%d" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.extra_data = {} def _normalize_coerce_order_by(self, value): if isinstance(value, OrderBy): return value return OrderBy.fromstring(value) def _normalize_coerce_sha256hash(self, value): return str(value).strip().lower() def _normalize_coerce_scan_db(self, value): if ANTIVIRUS_MODE: return False if value is None: return Malware.DATABASE_SCAN_ENABLED return value def _validate_type_order_by(self, value): if isinstance(value, OrderBy): return True return False def _validate_type_sha256hash(self, value: str): return SHA256_REGEXP.match(str(value).strip()) def _validate_is_absolute_path(self, is_absolute_path, field, value): """{'type': 'boolean', 'empty': False}""" if is_absolute_path: if not os.path.isabs(value): self._error(field, "Path {} should be absolute".format(value)) def _validate_isascii(self, isascii, field, value): """{'type': 'boolean'}""" if isascii: try: value.encode("ascii") except UnicodeEncodeError: self._error(field, "Must only contain ascii symbols") def _normalize_coerce_int(self, value): return int(value) def _normalize_default_setter_now(self, document) -> int: return math.ceil(datetime.datetime.now().timestamp()) # for argparser support def _validate_cli(self, *args, **kwargs): """{'type': 'dict', 'empty': False, 'schema': { 'users': {'type': 'list', 'allowed': ['non-root', 'root'], 'empty': False}, 'require_rpc': {'type': 'string', 'empty': True, 'default': 'running', 'allowed': ['running', 'stopped', 'any', 'direct']} }} """ # for argparser support def _validate_help(self, *args, **kwargs): """{'type': 'string', 'empty': False}""" # for argparser support def _validate_positional(self, *args, **kwargs): """{'type': 'boolean', 'empty': True, 'default': False}""" # metadata for response validation def _validate_return_type(self, *args, **kwargs): """{'type': 'string', 'empty': True}""" def _validate_cli_only(self, *args, **kwargs): """{'type': 'boolean', 'empty': False, 'default': False}""" def _validate_envvar(self, *args, **kwargs): """ Parameter can be passed via the specified environment variable. The value specified via a CLI argument takes precedence. The rule's arguments are validated against this schema: {'type': 'string', 'empty': False} """ def _validate_envvar_only(self, *args, **kwargs): """ Parameter will only be accepted if provided via environment variable specified by `envvar`. It will be rejected if passed as a CLI argument. The rule's arguments are validated against this schema: {'type': 'boolean', 'default': False} """ def _normalize_coerce_path(self, value: str): if value: return os.path.abspath(value) return value def _normalize_coerce_backup_system(self, value): if isinstance(value, BackupSystem): return value return get_backend(value) def _validator_backup_is_enabled(self, field, value): if not (BackupRestore.ENABLED and BackupRestore.backup_system()): self._error(field, "Backup is not enabled!") def _validator_timestamp(self, field: str, value: int) -> None: try: datetime.datetime.fromtimestamp(value) except ValueError as e: self._error(field, f"Incorrect timestamp: {value} ({str(e)})") def validate(validator, hashable, params): values = validator.normalized( {hashable: params}, always_return_document=True ) if not validator.validate({hashable: values[hashable]}): logger.warning( "Validation error with command {}, params {}, errors {}".format( hashable, params, validator.errors ) ) raise ValidationError(validator.errors, validator.extra_data) return validator.document[hashable] def validate_middleware(validator): def wrapped(f): @wraps(f) async def wrapper(request, *args, **kwargs): hashable = tuple(request["command"]) request["params"] = validate( validator, hashable, request["params"] ) result = await f(request, *args, **kwargs) return result return wrapper return wrapped def validate_av_plus_license(func): """ Decorator for CLI commands methods that ensures that the AV+ license is valid. :raises ValidationError: """ exception = ValidationError("ImunifyAV+ license required") @wraps(func) async def async_wrapper(*args, **kwargs): if LicenseCLN.is_valid_av_plus(): return await func(*args, **kwargs) raise exception if ANTIVIRUS_MODE: return async_wrapper return func defence360agent/run.py0000644000000000000000000000015500000000000011725 0ustar CORE_PLUGINS_PACKAGES = ( "defence360agent.plugins", "defence360agent.feature_management.plugins", ) defence360agent/sentry.py0000644000000000000000000001201100000000000012437 0ustar """Helper for integrate sentry in stand-alone scripts""" import json import os import subprocess from contextlib import suppress from pathlib import Path from typing import List, Optional, Literal import distro import sentry_sdk from defence360agent.application import tags from defence360agent.contracts import sentry IMUNIFY360 = "imunify360" IMUNIFYAV = "imunify-antivirus" IMUNIFY360_PKG = "imunify360-firewall" LICENSE = "/var/imunify360/license.json" LICENSE_FREE = "/var/imunify360/license-free.json" FREE_ID = "IMUNIFYAV" UNKNOWN_ID = "UNKNOWN" SENTRY_DSN_PATH = Path("/opt/imunify360/venv/share/imunify360/sentry") SENTRY_DSN_DEFAULT = "https://6de77a2763bd40c58fc9e3a89285aaa8@im360.sentry.cloudlinux.com/3?timeout=20" # noqa: E501 def get_sentry_dsn() -> str: """Return dsn from the file or the default one.""" try: return SENTRY_DSN_PATH.read_text(encoding="ascii").strip() except (OSError, UnicodeDecodeError): return SENTRY_DSN_DEFAULT def get_server_id() -> str: with suppress(Exception): for filename in [LICENSE, LICENSE_FREE]: with suppress(FileNotFoundError), open(filename) as file: return json.load(file)["id"] return UNKNOWN_ID def collect_output(cmd: List[str]) -> str: try: cp = subprocess.run( cmd, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ) except OSError: return "" if cp.returncode != 0: return "" return os.fsdecode(cp.stdout) def get_rpm_version(pkg: str) -> str: cmd = ["rpm", "-q", "--queryformat=%{VERSION}-%{RELEASE}", pkg] return collect_output(cmd) def get_dpkg_version(pkg: str) -> str: cmd = ["dpkg-query", "--showformat=${Version}", "--show", pkg] return collect_output(cmd) def get_current_os(): platform_os = distro.linux_distribution()[0] return platform_os.lower() def get_package_name(): platform_os = get_current_os() service_name = IMUNIFY360_PKG if platform_os != "ubuntu" and get_rpm_version(IMUNIFYAV): service_name = IMUNIFYAV else: service_name = IMUNIFY360 return service_name def get_service_version(service_name) -> str: platform_os = get_current_os() if platform_os != "ubuntu": version = get_rpm_version(service_name) else: version = get_dpkg_version(service_name) return version def configure_sentry(): # using LoggingIntegration (contained in default Integrations) # logging event with *error* level will be reported to Sentry automatically sentry_sdk.init(dsn=get_sentry_dsn()) with sentry_sdk.configure_scope() as scope: package = get_package_name() scope.user = {"id": get_server_id()} scope.set_tag("name", package) scope.set_tag("version", get_service_version(package)) tags.cached_fill() for tag, value in sentry.tags().items(): scope.set_tag(tag, value) def flush_sentry(): client = sentry_sdk.Hub.current.client if client is not None: client.flush(timeout=2.0) def log_message( message: str, format_args: Optional[dict] = None, level: Literal[ "fatal", "critical", "error", "warning", "info", "debug" ] = "warning", fingerprint: Optional[str] = None, component: Optional[str] = None, **kwargs ): """ Helper function to log messages to Sentry with optional fingerprinting. This is useful when you need to log messages to Sentry without relying on error handling. Args: message: The message to log format_args: Dictionary of arguments to format the message with (optional) level: Log level (default: "warning") fingerprint: String for Sentry fingerprinting (optional) component: Component name to tag the message with (optional) **kwargs: Additional keyword arguments passed to sentry_sdk.capture_message() Common options include: - extra: dict of extra data to include - tags: dict of additional tags - contexts: dict of additional contexts """ if format_args is None: format_args = {} # Only format the message if format_args is not empty if format_args: try: formatted_message = message.format(**format_args) except KeyError: # If formatting fails due to missing keys, use the original message formatted_message = message else: formatted_message = message # Remove 'level' from kwargs if present to avoid conflicts kwargs.pop("level", None) if fingerprint or component: with sentry_sdk.push_scope() as scope: if fingerprint: scope.fingerprint = [fingerprint] if component: scope.set_tag("component", component) sentry_sdk.capture_message( formatted_message, level=level, **kwargs ) else: sentry_sdk.capture_message(formatted_message, level=level, **kwargs) defence360agent/simple_rpc/0000755000000000000000000000000000000000000012703 5ustar defence360agent/simple_rpc/__init__.py0000644000000000000000000005442500000000000015026 0ustar """ Simple unix socket RPC server implementation """ import asyncio import functools import inspect import io import json import os import select import socket import struct import sys import time from contextlib import suppress from logging import getLogger from typing import Sequence from psutil import Process import sentry_sdk from defence360agent.api import inactivity from defence360agent.application import app from defence360agent.contracts.config import Core, SimpleRpc as Config from defence360agent.feature_management.exceptions import ( FeatureManagementError, ) from defence360agent.internals.auth_protocol import UnixSocketAuthProtocol from defence360agent.model import tls_check from defence360agent.model.simplification import run_in_executor from defence360agent.utils import is_root_user, run_coro from defence360agent.utils.buffer import LineBuffer, LineBufferOverflow from defence360agent.subsys.panels import hosting_panel from defence360agent.subsys.panels.base import InvalidTokenException from defence360agent.subsys import svcctl from defence360agent.rpc_tools.exceptions import ( ResponseError, ServiceStateError, SocketError, ) from defence360agent.rpc_tools.lookup import Endpoints, UserType from defence360agent.rpc_tools.utils import ( is_running, # noqa: F401 rpc_is_running, ) from defence360agent.rpc_tools.validate import ValidationError # caller_uid_var is re-exported for existing importers; it lives in # rpc_tools so lower layers never import from simple_rpc. from defence360agent.rpc_tools import ( ERROR, SUCCESS, WARNING, caller_uid_var, ) logger = getLogger(__name__) _SENSITIVE_PARAM_KEYS = frozenset({"jwt", "token", "password"}) def _redact_for_log(decoded): safe = dict(decoded) params = safe.get("params") if isinstance(params, dict): safe_params = dict(params) for key in _SENSITIVE_PARAM_KEYS: if key in safe_params: safe_params[key] = "***" safe["params"] = safe_params return safe def _safe_log_payload(raw): try: decoded = json.loads(raw) except Exception: return "".format(len(raw)) if not isinstance(decoded, dict): return repr(decoded) return repr(_redact_for_log(decoded)) class RpcServiceState: # If need DB and agent should be running # e.g. on-demand scan RUNNING = "running" # Agent should be stopped STOPPED = "stopped" # It doesn't matter for operation running or stopping the agent # if agent is running - using socket, instead of direct communication ANY = "any" # No need DB and UI interaction # preferable for use direct instead any for execution external process # e.g. enable/disable plugins/features DIRECT = "direct" async def _execute_request(coro, method): try: result = await coro except ValidationError as e: result = { "result": WARNING, "messages": e.errors, } result.update(e.extra_data) return result except (PermissionError, FeatureManagementError) as e: msg, *args = e.args logger.error(msg, *args) return { "result": ERROR, "messages": [msg % tuple(args)], } except Exception as e: sentry_sdk.capture_exception(e) logger.error( "Something went wrong while processing %s (%s)", method, str(e) ) return {"result": ERROR, "messages": str(e)} else: return {"result": SUCCESS, "messages": [], "data": result} def _apply_middleware(method, user): cb = Endpoints.route_to_endpoint if isinstance(method, (list, tuple)): hashable = tuple(method) common = app.MIDDLEWARE.get(None, []) specific = app.MIDDLEWARE.get(hashable, []) excluded = app.MIDDLEWARE_EXCLUDE.get(hashable, []) for mw, users in reversed(common + specific): if (user in users) and (mw not in excluded): logger.debug("Applying middleware %s", mw.__name__) cb = mw(cb) return cb def _find_uds_inodes(socket_path: str) -> Sequence[str]: """Find inodes corresponding to the unix domain socket path.""" with open( "/proc/net/unix", encoding=sys.getfilesystemencoding(), errors=sys.getfilesystemencodeerrors(), ) as file: return [line.split()[-2] for line in file if socket_path in line] def _protocol_supports_guard(protocol_cls): """True if cls.__init__ accepts the guard kwargs; legacy *_ signatures TypeError when passed limiter=.""" try: sig = inspect.signature(protocol_cls.__init__) except (TypeError, ValueError): return False params = sig.parameters return "limiter" in params and "read_timeout" in params class ConnectionLimiter: def __init__(self, max_connections): self.max_connections = max_connections self._count = 0 self.saturation_logged = False def acquire(self): if self._count >= self.max_connections: return False self._count += 1 return True def release(self): if self._count > 0: self._count -= 1 self.saturation_logged = False @property def count(self): return self._count class ConnectionGuard: def __init__(self, loop, *, limiter=None, read_timeout=None, name): self._loop = loop self._limiter = limiter self._read_timeout = read_timeout self._name = name self._transport = None self._timeout_handle = None self._slot_acquired = False self._peer_pid = None self._peer_uid = None def try_admit(self, transport): if self._limiter is not None and not self._limiter.acquire(): if not self._limiter.saturation_logged: logger.warning( "%s connection limit (%d) reached; rejecting new client", self._name, self._limiter.max_connections, ) self._limiter.saturation_logged = True return False self._slot_acquired = self._limiter is not None self._transport = transport self._schedule_timeout() return True def note_peer(self, pid, uid): self._peer_pid = pid self._peer_uid = uid def on_data(self): self._schedule_timeout() def on_lost(self): self._cancel_timeout() if self._slot_acquired and self._limiter is not None: self._limiter.release() self._slot_acquired = False self._transport = None def _schedule_timeout(self): if self._read_timeout is None: return if self._timeout_handle is not None: self._timeout_handle.cancel() self._timeout_handle = self._loop.call_later( self._read_timeout, self._on_timeout ) def _cancel_timeout(self): if self._timeout_handle is not None: self._timeout_handle.cancel() self._timeout_handle = None def _on_timeout(self): self._timeout_handle = None if self._transport is None: return logger.warning( "Closing idle %s connection (pid=%s uid=%s, no data for %ds)", self._name, self._peer_pid, self._peer_uid, self._read_timeout, ) transport, self._transport = self._transport, None transport.close() self.on_lost() class _RpcServerProtocol(UnixSocketAuthProtocol): def __init__(self, loop, sink, user, *, limiter=None, read_timeout=None): self._loop = loop self._sink = sink self.user = user self._transport = None self._buf = LineBuffer() self._guard = ConnectionGuard( loop, limiter=limiter, read_timeout=read_timeout, name="RPC" ) def connection_made(self, transport): if not self._guard.try_admit(transport): transport.close() return try: super().connection_made(transport) except (OSError, AttributeError, struct.error) as exc: logger.warning( "Rejected RPC connection: SO_PEERCRED unavailable (%s)", exc, ) transport.close() self._transport = None self._guard.on_lost() return self._guard.note_peer(self._pid, self._uid) def preprocess_data(self, data: str): decoded = json.loads(data) user_type, user_name = hosting_panel.HostingPanel().authenticate( self, decoded ) self.user = user_type if user_name is not None: decoded["params"]["user"] = user_name # Prevent multi-user bypass: non-root callers may only operate on # their own username even when 'users' (plural) is supplied. if "users" in decoded["params"]: decoded["params"]["users"] = [user_name] # add calling process try: calling_process = Process(self._pid).cmdline() except Exception as e: calling_process = [str(e)] decoded["calling_process"] = calling_process return decoded def data_received(self, data): if self._transport is None: return self._guard.on_data() try: self._buf.append(data.decode()) except LineBufferOverflow as e: logger.warning( "Closing RPC connection (pid=%s uid=%s): %s", self._pid, self._uid, e, ) self._transport.close() self._transport = None self._guard.on_lost() return for msg in self._buf: try: result = self.preprocess_data(msg) method = result["command"] params = result["params"] logger.debug("Data received: command=%s", method) cb = _apply_middleware(method, self.user) # Scope caller_uid_var to the create_task call so the new # task captures it via copy_context, but the parent # protocol context is left untouched -- preventing leakage # into subsequent reads (tests, repeated requests, etc.). token = caller_uid_var.set(self._uid) try: # TODO: fix that there is no json flag in params self._loop.create_task( self._dispatch( method, params, cb(result, self._sink, self.user) ) ) finally: caller_uid_var.reset(token) except InvalidTokenException as e: # without events in Sentry logger.warning("Incorrect token provided") self._write_response({"result": ERROR, "messages": str(e)}) except Exception as e: logger.exception( "Something went wrong before processing %s", _safe_log_payload(msg), ) self._write_response({"result": ERROR, "messages": str(e)}) async def _dispatch(self, method, params, coro): with inactivity.track.task("rpc_{}".format(method)): # route and save result to 'result' response = await _execute_request(coro, method) logger.info( "Response: method - {}, data - {}".format(method, response) ) self._write_response(response) def connection_lost(self, transport): self._guard.on_lost() self._transport = None def _write_response(self, data): if self._transport is None: logger.warning("Cannot send RPC response: connection lost.") return else: try: self._transport.write((json.dumps(data) + "\n").encode()) except Exception as e: logger.exception(e) # TODO: need to own message error def _check_socket_folder_permissions(socket_path): dir_name = os.path.dirname(socket_path) os.makedirs(dir_name, exist_ok=True) os.chmod(dir_name, 0o755) class RpcServer: SOCKET_PATH = Config.SOCKET_PATH USER = UserType.ROOT SOCKET_MODE = 0o700 @classmethod async def create(cls, loop, sink): _check_socket_folder_permissions(cls.SOCKET_PATH) with suppress(FileNotFoundError): os.unlink(cls.SOCKET_PATH) limiter = ConnectionLimiter(Config.MAX_CONCURRENT_CONNECTIONS) server = await loop.create_unix_server( lambda: _RpcServerProtocol( loop, sink, cls.USER, limiter=limiter, read_timeout=Config.READ_TIMEOUT, ), cls.SOCKET_PATH, ) os.chmod(cls.SOCKET_PATH, cls.SOCKET_MODE) return server class RpcServerAV: USER = UserType.ROOT SOCKET_PATH = Config.SOCKET_PATH PROTOCOL_CLASS = _RpcServerProtocol @classmethod async def create(cls, loop, sink): """Looking for socket in /proc/net/unix and check which descriptor corresponded to it by comparing inode $ ls -l /proc/[pid]/fd lrwx------ 1 root root 64 Apr 11 07:20 4 -> socket:[2866765] $ cat /proc/net/unix Num RefCount Protocol Flags Type St Inode Path ffff880054c0a4c0: 00000002 00000000 00010000 0001 01 2866765 /var/run/defence360agent/simple_rpc.sock # noqa """ def safe_readlink(*args, **kwargs): """Return empty path on error.""" with suppress(OSError): return os.readlink(*args, **kwargs) return "" # find inodes for the SOCKET_PATH _socket_path = cls.SOCKET_PATH _check_socket_folder_permissions(_socket_path) if _socket_path.startswith("/var/run"): # remove /var prefix, see DEF-16201 _socket_path = _socket_path[len("/var") :] inodes = _find_uds_inodes(_socket_path) # find socket fds corresponding to the inodes last_error = None for inode in inodes: try: with os.scandir("/proc/self/fd") as it: for fd in it: if safe_readlink(fd.path) == "socket:[{}]".format( inode ): socket_fd = int(fd.name) break # found fd else: # no break, not found fd for given inode continue # try another inode break # found fd except OSError as e: last_error = e else: # no break, not found raise SocketError( "[{}] Socket {!r} for {} not found.".format( "inode" * (not inodes), cls.SOCKET_PATH, cls.USER ) ) from last_error _socket = socket.fromfd( socket_fd, socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK, ) if _protocol_supports_guard(cls.PROTOCOL_CLASS): limiter = ConnectionLimiter(Config.MAX_CONCURRENT_CONNECTIONS) factory = lambda: cls.PROTOCOL_CLASS( # noqa: E731 loop, sink, cls.USER, limiter=limiter, read_timeout=Config.READ_TIMEOUT, ) else: factory = lambda: cls.PROTOCOL_CLASS( # noqa: E731 loop, sink, cls.USER ) server = await loop.create_unix_server(factory, sock=_socket) return server class NonRootRpcServerAV(RpcServerAV): USER = UserType.NON_ROOT SOCKET_PATH = Config.NON_ROOT_SOCKET_PATH class NonRootRpcServer(RpcServer): SOCKET_PATH = Config.NON_ROOT_SOCKET_PATH USER = UserType.NON_ROOT # Match the systemd .socket unit (SocketMode=0666). UNIX domain sockets # don't use the execute bit, so granting it (the previous 0o777) only # widened the attack surface without enabling any client. SOCKET_MODE = 0o666 class _RpcClientImpl: def __init__(self, socket_path): try: self._sock = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK ) self._sock.connect(socket_path) except (ConnectionRefusedError, FileNotFoundError, BlockingIOError): raise ServiceStateError() def dispatch(self, method, params): try: self._sock.sendall( ( json.dumps({"command": method, "params": params}) + "\n" ).encode() ) except BrokenPipeError as e: raise SocketError(f"communication interrupted, {e}") try: data = self._sock_recv_until(terminator_byte=b"\n") except ConnectionResetError as e: raise ResponseError(f"Connection reset: {e}") from e try: response = json.loads(data.decode()) except Exception as e: raise ResponseError( "Error parsing RPC response {!r}".format(data) ) from e return response def _sock_recv_until(self, terminator_byte): assert not self._sock.getblocking() chunks = [] while (not chunks) or (terminator_byte not in chunks[-1]): fdread_list = [self._sock.fileno()] rwx_fdlist = select.select( fdread_list, [], [], # naive timeout for one-shot response # scenario Config.CLIENT_TIMEOUT, ) fdready_list = rwx_fdlist[0] if self._sock.fileno() not in fdready_list: if any(rwx_fdlist): raise SocketError( "select() = {!r} resulted in error".format(rwx_fdlist) ) else: raise SocketError("request timeout") chunk = self._sock.recv(io.DEFAULT_BUFFER_SIZE) if len(chunk) == 0: raise SocketError("Empty response from socket.recv()") chunks.append(chunk) return b"".join(chunks) class _NoRpcImpl: def __init__(self, sink=None): self._sink = sink # suppress is for doing those things idempotent way # PSSST! simplification.run_in_executor() is main thread now! :-X # with suppress(tls_check.OverridingReset): # tls_check.reset("main CLI thread for stopped agent") with suppress(tls_check.OverridingReset): loop = asyncio.get_event_loop() loop.run_until_complete(run_in_executor(loop, tls_check.reset)) def dispatch(self, method, params): loop = asyncio.get_event_loop() logger.info("Executing {}, params: {}".format(method, params)) request = {"command": method, "params": params} token = caller_uid_var.set(os.getuid()) try: cb = _apply_middleware(method, user=UserType.ROOT) return loop.run_until_complete( _execute_request(cb(request, self._sink), method) ) finally: caller_uid_var.reset(token) class RpcClient: """ One RpcClient instance is suitable to use for multiple ipc calls :param RpcServiceState require_svc_is_running: whether to provide direct endpoints binding if the service is stopped. :param int reconnect_with_timeout: timeout in sec for reconnect retries :param int num_retries: number of reconnect retries """ def __init__( self, *, require_svc_is_running=RpcServiceState.RUNNING, reconnect_with_timeout=None, num_retries=1, ): self._impl = None self._socket_path = ( Config.SOCKET_PATH if is_root_user() else Config.NON_ROOT_SOCKET_PATH ) if ( require_svc_is_running == RpcServiceState.STOPPED and rpc_is_running() ): raise ServiceStateError(RpcServiceState.RUNNING) elif require_svc_is_running == RpcServiceState.RUNNING: # ensure that socket is active run_coro(svcctl.activate_socket_service(Core.SVC_NAME)) if require_svc_is_running in ( RpcServiceState.ANY, RpcServiceState.RUNNING, ): try: if reconnect_with_timeout: self._impl = self._reconnect_with_timeout( reconnect_with_timeout, num_retries ) else: self._impl = _RpcClientImpl(self._socket_path) return except ServiceStateError: if require_svc_is_running == RpcServiceState.RUNNING: raise if self._impl is None: # In other cases (ANY, STOPPED, DIRECT) need to use _NoRpcImpl assert ( is_root_user() ), "_NoRpcImpl is not available for non root user" self._impl = _NoRpcImpl() def __getattr__(self, method): return functools.partial(self._dispatch, method) def cmd(self, *command): return functools.partial(self._dispatch, command) def _dispatch(self, method, **params): response = self._impl.dispatch(method, params) if isinstance(method, (list, tuple)): if response["result"] in (ERROR, WARNING): return response["result"], response["messages"] else: assert response["result"] == SUCCESS return response["result"], response["data"] else: if response["result"] in (ERROR, WARNING): raise ResponseError(response["messages"]) return response["data"] def _reconnect_with_timeout(self, timeout, num_retries): while True: try: return _RpcClientImpl(self._socket_path) except ServiceStateError: if num_retries: logger.info( "Waiting %d second(s) before retry...", timeout ) time.sleep(timeout) num_retries -= 1 else: raise defence360agent/simple_rpc/__pycache__/0000755000000000000000000000000000000000000015113 5ustar defence360agent/simple_rpc/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000010550200000000000022316 0ustar r_jY"dZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl m Z ddlmZddlmZddlmZddlZddlmZddlmZdd lmZmZdd lmZdd lm Z dd l!m"Z"dd l#m$Z$ddl%m&Z&m'Z'ddl(m)Z)m*Z*ddl+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2m3Z3m4Z4ddl5m6Z6m7Z7ddl8m9Z9m:Z:ddl;mZ>m?Z?m@Z@mAZAeeBZCeDhdZEdZFdZGGddZHdZIdZJdeKd eeKfd!ZLd"ZMGd#d$ZNGd%d&ZOGd'd(e ZPd)ZQGd*d+ZRGd,d-ZSGd.d/eSZTGd0d1eRZUGd2d3ZVGd4d5ZWGd6d7ZXdS)8z. Simple unix socket RPC server implementation N)suppress) getLogger)Sequence)Process) inactivity)app)Core SimpleRpc)FeatureManagementError)UnixSocketAuthProtocol) tls_check)run_in_executor) is_root_userrun_coro) LineBufferLineBufferOverflow) hosting_panel)InvalidTokenException)svcctl) ResponseErrorServiceStateError SocketError) EndpointsUserType) is_runningrpc_is_running)ValidationError)ERRORSUCCESSWARNINGcaller_uid_var>jwttokenpasswordct|}|d}t|tr't|}tD] }||vrd||< ||d<|S)Nparamsz***)dictget isinstance_SENSITIVE_PARAM_KEYS)decodedsafer& safe_paramskeys X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/__init__.py_redact_for_logr0@sn ==D XXh  F&$%6ll ( ) )Ck!!#( C $X Kc tj|}n2#t$r%dt |cYSwxYwt |t st|Stt|S)Nz) jsonloads Exceptionformatlenr)r'reprr0)rawr+s r/_safe_log_payloadr:Ls:*S// :::(//C99999: gt $ $G}} (( ) ))s,AAceZdZdZdZdZdZdS)RpcServiceStaterunningstoppedanydirectN)__name__ __module__ __qualname__RUNNINGSTOPPEDANYDIRECTr1r/r<r<Vs*GG C FFFr1r<cK |d{V}tg|dS#t$r5}t|jd}||j|cYd}~Sd}~wt tf$r@}|j^}}tj |g|Rt|t|zgdcYd}~Sd}~wt$r^}tj|t d|t!|tt!|dcYd}~Sd}~wwxYw)N)resultmessagesdatarJrKz-Something went wrong while processing %s (%s))rrr errorsupdate extra_dataPermissionErrorr argsloggererrorrtupler5 sentry_sdkcapture_exceptionstr)coromethodrJemsgrRs r/_execute_requestr]hsuC."r6BBB-     al### 3 4   V d S 4    uT{{*+         555$Q''' ;VSVV    SVV44444444 5s? D *A D D %5B D  D -ADD D ctj}t|ttfrt |}t jdg}t j|g}t j|g}t||zD]8\}}||vr/||vr+t d|j ||}9|S)NzApplying middleware %s) rroute_to_endpointr)listrUr MIDDLEWAREr(MIDDLEWARE_EXCLUDEreversedrSdebugrA) rZusercbhashablecommonspecificexcludedmwuserss r/_apply_middlewarerms  $B&4-((==##D"-->%%h33)--h;;!&8"344  IB Bh$6$6 5r{CCCRVV Ir1 socket_pathreturnctdtjtj5}fd|DcdddS#1swxYwYdS)z9Find inodes corresponding to the unix domain socket path.z/proc/net/unix)encodingrNcLg|] }|v|d!S))split).0linerns r/ z$_find_uds_inodes..s0IIIT[D5H5H R 5H5H5Hr1N)opensysgetfilesystemencodinggetfilesystemencodeerrors)rnfiles` r/_find_uds_inodesr}s *,,,..   J IIIITIII JJJJJJJJJJJJJJJJJJsAAAc tj|j}n#ttf$rYdSwxYw|j}d|vod|vS)zcTrue if cls.__init__ accepts the guard kwargs; legacy *_ signatures TypeError when passed limiter=.Flimiter read_timeout)inspect signature__init__ TypeError ValueError parameters) protocol_clssigr&s r/_protocol_supports_guardrs_ 566 z "uu ^F   ;>V#;;s 11c6eZdZdZdZdZedZdS)ConnectionLimiterc0||_d|_d|_dS)NrF)max_connections_countsaturation_logged)selfrs r/rzConnectionLimiter.__init__s. !&r1cJ|j|jkrdS|xjdz c_dS)NFT)rrrs r/acquirezConnectionLimiter.acquires, ;$. . .5 q tr1cN|jdkr|xjdzc_d|_dSdS)NrrF)rrrs r/releasezConnectionLimiter.releases2 ;?? KK1 KK%*D " " " ?r1c|jSN)rrs r/countzConnectionLimiter.counts {r1N)rArBrCrrrpropertyrrHr1r/rrs\'''  +++ Xr1rcFeZdZddddZdZdZdZdZdZd Z d Z dS) ConnectionGuardNrrc||_||_||_||_d|_d|_d|_d|_d|_dSNF) _loop_limiter _read_timeout_name _transport_timeout_handle_slot_acquired _peer_pid _peer_uid)rlooprrnames r/rzConnectionGuard.__init__sH  ) ##r1c"|j^|jsE|jjs7td|j|jjd|j_dS|jdu|_||_| dS)Nz6%s connection limit (%d) reached; rejecting new clientTF) rrrrSwarningrrrr_schedule_timeoutr transports r/ try_admitzConnectionGuard.try_admits = $T]-B-B-D-D $=2 7LJM1 37 /5"m47#    tr1c"||_||_dSr)rr)rpiduids r/ note_peerzConnectionGuard.note_peersr1c.|dSr)rrs r/on_datazConnectionGuard.on_datas      r1c||jr'|j |jd|_d|_dSr)_cancel_timeoutrrrrrs r/on_lostzConnectionGuard.on_lostsM    (4=#< M ! ! # # #"'D r1c|jdS|j|j|j|j|j|_dSr)rrcancelr call_later _on_timeoutrs r/rz!ConnectionGuard._schedule_timeoutsY   % F   +  ' ' ) ) )#z44   0  r1cX|j"|jd|_dSdSr)rrrs r/rzConnectionGuard._cancel_timeouts6   +  ' ' ) ) )#'D  , +r1cd|_|jdStd|j|j|j|j|jdc}|_|| dS)Nz;Closing idle %s connection (pid=%s uid=%s, no data for %ds)) rrrSrrrrrcloserrs r/rzConnectionGuard._on_timeoutsx# ? " F I J N N      &*_d" 4? r1) rArBrCrrrrrrrrrHr1r/rrs(,4        !!!   (((     r1rcPeZdZddddZfdZdefdZdZdZd Z d Z xZ S) _RpcServerProtocolNrc||_||_||_d|_t |_t |||d|_dS)NRPC)rrr)r_sinkrerr_bufr_guard)rrsinkrerrs r/rz_RpcServerProtocol.__init__sL   LL % ' 5    r1c|j|s|dS t|ny#t t tjf$rZ}t d||d|_ |j Yd}~dSd}~wwxYw|j |j|jdS)Nz5Rejected RPC connection: SO_PEERCRED unavailable (%s))rrrsuperconnection_madeOSErrorAttributeErrorstructrTrSrrrr_pid_uid)rrexc __class__s r/rz"_RpcServerProtocol.connection_mades{$$Y//  OO    F  GG # #I . . . .6    NNG    OO   "DO K   ! ! ! FFFFF  di33333s!AC 1ACC rLc~tj|}tj||\}}||_|!||dd<d|dvr |g|dd< t |j}n'#t$r}t|g}Yd}~nd}~wwxYw||d<|S)Nr&rerlcalling_process) r3r4r HostingPanel authenticatererrcmdliner5rX)rrLr+ user_type user_namerr[s r/preprocess_dataz"_RpcServerProtocol.preprocess_datas*T"",9;;HH '   9  (1GH f %'(+++.7[!'* '%di0088::OO ' ' '"1vvhOOOOOO '%4!"s*&B B5B00B5c |jdS|j |j|nx#t $rk}td|j |j ||j d|_|j Yd}~dSd}~wwxYw|jD]} | |}|d}|d}td|t||j}t#j|j } |j||||||j|jt#j|n#t#j|wxYw#t0$rO}td|t4t7|dYd}~:d}~wt8$r]}tdt=||t4t7|dYd}~d}~wwxYwdS)Nz*Closing RPC connection (pid=%s uid=%s): %scommandr&zData received: command=%szIncorrect token providedrMz)Something went wrong before processing %s)rrrrappenddecoderrSrrrrrrrdrmrer!setr create_task _dispatchrresetr_write_responserrXr5 exceptionr:) rrLr[r\rJrZr&rfr#s r/ data_receivedz _RpcServerProtocol.data_received4s ? " F   I  T[[]] + + + +!    NN<       O ! ! # # #"DO K   ! ! ! FFFFF 9$ L$ LC# L--c22 *) 8&AAA&vty99 '*49550J**"FBBvtz49,M,M #(////N(/////( L L L9:::$$3q66%J%JKKKKKKKK L L L  ?%c** $$3q66%J%JKKKKKKKK  L=$ L$ LsX,A CA CCA.F6AFF6F22F66 I4AH  I4AI//I4cRKtjd|5t ||d{V}t d||||ddddS#1swxYwYdS)Nzrpc_{}z Response: method - {}, data - {})rtracktaskr6r]rSinfor)rrZr&rYresponses r/rz_RpcServerProtocol._dispatchks   " "8??6#:#: ; ; + +-dF;;;;;;;;H KK299&(KK      * * *  + + + + + + + + + + + + + + + + + +sABB #B cF|jd|_dSr)rrrrs r/connection_lostz"_RpcServerProtocol.connection_lostts! r1c4|jtddS |jt j|dzdS#t$r%}t|Yd}~dSd}~wwxYw)Nz*Cannot send RPC response: connection lost. ) rrSrwriter3dumpsencoder5r)rrLr[s r/rz"_RpcServerProtocol._write_responsexs ? " NNG H H H F $%%tz$'7'7$'>&F&F&H&HIIIII $ $ $  ######### $sAA(( B2BB) rArBrCrrrXrrrrr __classcell__)rs@r/rrs48t     44444"C*5L5L5Ln+++$$$$$$$r1rctj|}tj|dtj|ddS)NT)exist_oki)ospathdirnamemakedirschmod)rndir_names r/ _check_socket_folder_permissionsrsBw{++HK4((((HXur1cDeZdZejZejZdZe dZ dS) RpcServericKtjtt5t jjdddn #1swxYwYt tj fdjd{V}t j jj |S)NcJtjtjSNr)rUSERConfig READ_TIMEOUTclsrrrsr/z"RpcServer.create..s+&#0 r1) r SOCKET_PATHrFileNotFoundErrorrunlinkrrMAX_CONCURRENT_CONNECTIONScreate_unix_serverr SOCKET_MODE)rrrserverrs``` @r/createzRpcServer.creates(999 ' ( ( ' ' Ico & & & ' ' ' ' ' ' ' ' ' ' ' ' ' ' '#F$EFF..        O          #/222 sAAAN) rArBrCrrrROOTrr  classmethodrrHr1r/rrsA$K =DK[r1rcDeZdZejZejZeZ e dZ dS) RpcServerAVc~Kd}j}t||dr|tdd}t |}d}|D]} t jd5}|D]?} || jd|krt| j } n@ dddg dddnX#1swxYwY#t$r } | }Yd} ~ d} ~ wwxYwtdd| zjj |tj| tjtjtjz} t'jr"t+t,jfd } nfd } | | d{V}|S) aLooking for socket in /proc/net/unix and check which descriptor corresponded to it by comparing inode $ ls -l /proc/[pid]/fd lrwx------ 1 root root 64 Apr 11 07:20 4 -> socket:[2866765] $ cat /proc/net/unix Num RefCount Protocol Flags Type St Inode Path ffff880054c0a4c0: 00000002 00000000 00010000 0001 01 2866765 /var/run/defence360agent/simple_rpc.sock # noqa ctt5tj|i|cdddS#1swxYwYdS)zReturn empty path on error.N)rrrreadlink)rRkwargss r/ safe_readlinkz)RpcServerAV.create..safe_readlinks'"" 4 4{D3F33 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 42s 377z/var/runz/varNz /proc/self/fdz socket:[{}]z"[{}] Socket {!r} for {} not found.inodecVjtjSr)PROTOCOL_CLASSrrrrsr/rz$RpcServerAV.create..s0c00#0 1r1c<jSr)rr)rrrsr/rz$RpcServerAV.create..s c00dCHr1)sock)rr startswithr7r}rscandirrr6intrrrrsocketfromfdAF_UNIX SOCK_STREAM SOCK_NONBLOCKrrrrr r )rrrr _socket_pathinodes last_errorritfd socket_fdr[_socketfactoryr rs``` @r/rzRpcServerAV.creates    (666  " ": . . 7'F 6L!,//   E Z00 B !!(=11]5I5I!66),BG I!E  !                             4;;6z*COSX   -  N  !5 5   $C$6 7 7 '(IJJGGGG..wW.EEEEEEEE sI"C 6AC: C C C C C C C  C6*C11C6N) rArBrCrrrrrrrrrrHr1r/rrsF =D$K'NCC[CCCr1rc*eZdZejZejZdS)NonRootRpcServerAVN) rArBrCrNON_ROOTrrNON_ROOT_SOCKET_PATHrrHr1r/r/r/s  D-KKKr1r/c.eZdZejZejZdZ dS)NonRootRpcServeriN) rArBrCrr1rrr0rr rHr1r/r3r3s%-K  DKKKr1r3c eZdZdZdZdZdS)_RpcClientImplc tjtjtjtjz|_|j|dS#t ttf$rtwxYwr) r!r#r$r%_sockconnectConnectionRefusedErrorrBlockingIOErrorr)rrns r/rz_RpcClientImpl.__init__sy & 2V5I IDJ J  { + + + + +&(9?K & & &#%% % &s AA&A?c |jtj||ddzn$#t $r}t d|d}~wwxYw |d}n%#t$r}td||d}~wwxYw tj | }n5#t$r(}td ||d}~wwxYw|S)Nrr&rzcommunication interrupted,  )terminator_bytezConnection reset: zError parsing RPC response {!r})r7sendallr3rrBrokenPipeErrorr_sock_recv_untilConnectionResetErrorrr4rr5r6)rrZr&r[rLrs r/dispatchz_RpcClientImpl.dispatchsJ A J  J6VDDEEL&((      A A A?A??@@ @ A A(((??DD# A A A 8Q 8 899q @ A z$++--00HH   188>>   sHAA A(A##A(,B B% B  B%)&C D#C==Dc|jrJg}|r ||dvr|jg}tj|ggtj}|d}|j|vr@t |r"td|td|j tj }t|dkrtd| ||||dvd|S)Nrz!select() = {!r} resulted in errorzrequest timeoutz!Empty response from socket.recv()r1)r7 getblockingfilenoselectrCLIENT_TIMEOUTr?rr6recvioDEFAULT_BUFFER_SIZEr7rjoin)rr>chunks fdread_list rwx_fdlist fdready_listchunks r/rAz_RpcClientImpl._sock_recv_untilsG:))+++++ !fRj@@:,,../K% J&a=Lz  "",66z??9%;BB:NN&&7888JOOB$:;;E5zzQ!"EFFF MM% / !fRj@@2xxr1N)rArBrCrrCrArHr1r/r5r5sA&&&.     r1r5ceZdZddZdZdS) _NoRpcImplNc||_ttj5t j}|t|tjddddS#1swxYwYdSr) rrr OverridingResetasyncioget_event_looprun_until_completerr)rrrs r/rz_NoRpcImpl.__init__>s i/ 0 0 L L)++D  # #OD)/$J$J K K K L L L L L L L L L L L L L L L L L LsAA//A36A3ctj}td||||d}t jtj} t|tj }| t|||j|t j|S#t j|wxYw)NzExecuting {}, params: {}r<)re)rWrXrSrr6r!rrgetuidrmrrrYr]rr)rrZr&rrequestr#rfs r/rCz_NoRpcImpl.dispatchJs%'' .55ffEEFFF$77"29;;// ("6 >>>B** GTZ!8!8&AA   ' ' ' 'N  ' ' ' 's -A CC$r)rArBrCrrCrHr1r/rTrT=s; L L L L ( ( ( ( (r1rTcDeZdZdZejddddZdZdZdZ d Z dS) RpcClientaR One RpcClient instance is suitable to use for multiple ipc calls :param RpcServiceState require_svc_is_running: whether to provide direct endpoints binding if the service is stopped. :param int reconnect_with_timeout: timeout in sec for reconnect retries :param int num_retries: number of reconnect retries Nr)require_svc_is_runningreconnect_with_timeout num_retriescd|_tr tjn tj|_|t jkr'trtt j |t j kr+ttj tj|t jt j fvr[ |r||||_nt%|j|_dS#t$r|t j krYnwxYw|j-ts Jdt'|_dSdS)Nz-_NoRpcImpl is not available for non root user)_implrrrr1r&r<rErrrDrractivate_socket_servicer SVC_NAMErF_reconnect_with_timeoutr5rT)rr_r`ras r/rzRpcClient.__init__cse ~~ -F  ,  #o&= = =   >$O$;<< < #'> > > V3DMBB C C C !    #&    )C!%!=!=. ""DJJ"00A!B!BDJ$   )_-DDDED  :  ? ?> ? ?#DJJJ  s7C99DDc6tj|j|Sr functoolspartialr)rrZs r/ __getattr__zRpcClient.__getattr__s 888r1c6tj|j|Srrh)rrs r/cmdz RpcClient.cmds 999r1c h|j||}t|ttfrI|dt t fvr|d|dfS|dtksJ|d|dfS|dt t fvrt|d|dS)NrJrKrL) rcrCr)r`rUrr rr)rrZr&rs r/rzRpcClient._dispatchs:&&vv66 ftUm , , $!eW%555)8J+???)W4444)8F+;;;!eW%555#HZ$8999F# #r1c t|jS#t$r;|r5td|t j||dz}nYnwxYw^)NTz$Waiting %d second(s) before retry...r)r5r&rrSrtimesleep)rtimeoutras r/rfz!RpcClient._reconnect_with_timeouts  %d&7888$   KK>Jw'''1$KK K   sAAA) rArBrC__doc__r<rDrrkrmrrfrHr1r/r^r^Xs /6# ,&,&,&,&,&\999::: $ $ $     r1r^)YrsrWrirrKr3rrHr!rryrp contextlibrloggingrtypingrpsutilrrVdefence360agent.apirdefence360agent.applicationr defence360agent.contracts.configr r r-defence360agent.feature_management.exceptionsr 'defence360agent.internals.auth_protocolr defence360agent.modelr $defence360agent.model.simplificationrdefence360agent.utilsrrdefence360agent.utils.bufferrrdefence360agent.subsys.panelsr"defence360agent.subsys.panels.baserdefence360agent.subsysr$defence360agent.rpc_tools.exceptionsrrr defence360agent.rpc_tools.lookuprrdefence360agent.rpc_tools.utilsrr"defence360agent.rpc_tools.validaterdefence360agent.rpc_toolsrrr r!rArS frozensetr*r0r:r<r]rmrXr}rrrrrrrr/r3r5rTr^rHr1r/rs+ ******++++++FFFFFFFFKJJJJJ++++++@@@@@@88888888GGGGGGGG777777DDDDDD)))))) A@@@@@@@?>>>>> 8  " ">">">??   ***$CCC8   J#J(3-JJJJ<<<,DDDDDDDDN}$}$}$}$}$/}$}$}$@ 2IIIIIIIIX........ y> > > > > > > > B((((((((6ZZZZZZZZZZr1defence360agent/simple_rpc/__pycache__/__init__.cpython-311.pyc0000644000000000000000000010550200000000000021357 0ustar r_jY"dZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl m Z ddlmZddlmZddlmZddlZddlmZddlmZdd lmZmZdd lmZdd lm Z dd l!m"Z"dd l#m$Z$ddl%m&Z&m'Z'ddl(m)Z)m*Z*ddl+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2m3Z3m4Z4ddl5m6Z6m7Z7ddl8m9Z9m:Z:ddl;mZ>m?Z?m@Z@mAZAeeBZCeDhdZEdZFdZGGddZHdZIdZJdeKd eeKfd!ZLd"ZMGd#d$ZNGd%d&ZOGd'd(e ZPd)ZQGd*d+ZRGd,d-ZSGd.d/eSZTGd0d1eRZUGd2d3ZVGd4d5ZWGd6d7ZXdS)8z. Simple unix socket RPC server implementation N)suppress) getLogger)Sequence)Process) inactivity)app)Core SimpleRpc)FeatureManagementError)UnixSocketAuthProtocol) tls_check)run_in_executor) is_root_userrun_coro) LineBufferLineBufferOverflow) hosting_panel)InvalidTokenException)svcctl) ResponseErrorServiceStateError SocketError) EndpointsUserType) is_runningrpc_is_running)ValidationError)ERRORSUCCESSWARNINGcaller_uid_var>jwttokenpasswordct|}|d}t|tr't|}tD] }||vrd||< ||d<|S)Nparamsz***)dictget isinstance_SENSITIVE_PARAM_KEYS)decodedsafer& safe_paramskeys X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/__init__.py_redact_for_logr0@sn ==D XXh  F&$%6ll ( ) )Ck!!#( C $X Kc tj|}n2#t$r%dt |cYSwxYwt |t st|Stt|S)Nz) jsonloads Exceptionformatlenr)r'reprr0)rawr+s r/_safe_log_payloadr:Ls:*S// :::(//C99999: gt $ $G}} (( ) ))s,AAceZdZdZdZdZdZdS)RpcServiceStaterunningstoppedanydirectN)__name__ __module__ __qualname__RUNNINGSTOPPEDANYDIRECTr1r/r<r<Vs*GG C FFFr1r<cK |d{V}tg|dS#t$r5}t|jd}||j|cYd}~Sd}~wt tf$r@}|j^}}tj |g|Rt|t|zgdcYd}~Sd}~wt$r^}tj|t d|t!|tt!|dcYd}~Sd}~wwxYw)N)resultmessagesdatarJrKz-Something went wrong while processing %s (%s))rrr errorsupdate extra_dataPermissionErrorr argsloggererrorrtupler5 sentry_sdkcapture_exceptionstr)coromethodrJemsgrRs r/_execute_requestr]hsuC."r6BBB-     al### 3 4   V d S 4    uT{{*+         555$Q''' ;VSVV    SVV44444444 5s? D *A D D %5B D  D -ADD D ctj}t|ttfrt |}t jdg}t j|g}t j|g}t||zD]8\}}||vr/||vr+t d|j ||}9|S)NzApplying middleware %s) rroute_to_endpointr)listrUr MIDDLEWAREr(MIDDLEWARE_EXCLUDEreversedrSdebugrA) rZusercbhashablecommonspecificexcludedmwuserss r/_apply_middlewarerms  $B&4-((==##D"-->%%h33)--h;;!&8"344  IB Bh$6$6 5r{CCCRVV Ir1 socket_pathreturnctdtjtj5}fd|DcdddS#1swxYwYdS)z9Find inodes corresponding to the unix domain socket path.z/proc/net/unix)encodingrNcLg|] }|v|d!S))split).0linerns r/ z$_find_uds_inodes..s0IIIT[D5H5H R 5H5H5Hr1N)opensysgetfilesystemencodinggetfilesystemencodeerrors)rnfiles` r/_find_uds_inodesr}s *,,,..   J IIIITIII JJJJJJJJJJJJJJJJJJsAAAc tj|j}n#ttf$rYdSwxYw|j}d|vod|vS)zcTrue if cls.__init__ accepts the guard kwargs; legacy *_ signatures TypeError when passed limiter=.Flimiter read_timeout)inspect signature__init__ TypeError ValueError parameters) protocol_clssigr&s r/_protocol_supports_guardrs_ 566 z "uu ^F   ;>V#;;s 11c6eZdZdZdZdZedZdS)ConnectionLimiterc0||_d|_d|_dS)NrF)max_connections_countsaturation_logged)selfrs r/rzConnectionLimiter.__init__s. !&r1cJ|j|jkrdS|xjdz c_dS)NFT)rrrs r/acquirezConnectionLimiter.acquires, ;$. . .5 q tr1cN|jdkr|xjdzc_d|_dSdS)NrrF)rrrs r/releasezConnectionLimiter.releases2 ;?? KK1 KK%*D " " " ?r1c|jSN)rrs r/countzConnectionLimiter.counts {r1N)rArBrCrrrpropertyrrHr1r/rrs\'''  +++ Xr1rcFeZdZddddZdZdZdZdZdZd Z d Z dS) ConnectionGuardNrrc||_||_||_||_d|_d|_d|_d|_d|_dSNF) _loop_limiter _read_timeout_name _transport_timeout_handle_slot_acquired _peer_pid _peer_uid)rlooprrnames r/rzConnectionGuard.__init__sH  ) ##r1c"|j^|jsE|jjs7td|j|jjd|j_dS|jdu|_||_| dS)Nz6%s connection limit (%d) reached; rejecting new clientTF) rrrrSwarningrrrr_schedule_timeoutr transports r/ try_admitzConnectionGuard.try_admits = $T]-B-B-D-D $=2 7LJM1 37 /5"m47#    tr1c"||_||_dSr)rr)rpiduids r/ note_peerzConnectionGuard.note_peersr1c.|dSr)rrs r/on_datazConnectionGuard.on_datas      r1c||jr'|j |jd|_d|_dSr)_cancel_timeoutrrrrrs r/on_lostzConnectionGuard.on_lostsM    (4=#< M ! ! # # #"'D r1c|jdS|j|j|j|j|j|_dSr)rrcancelr call_later _on_timeoutrs r/rz!ConnectionGuard._schedule_timeoutsY   % F   +  ' ' ) ) )#z44   0  r1cX|j"|jd|_dSdSr)rrrs r/rzConnectionGuard._cancel_timeouts6   +  ' ' ) ) )#'D  , +r1cd|_|jdStd|j|j|j|j|jdc}|_|| dS)Nz;Closing idle %s connection (pid=%s uid=%s, no data for %ds)) rrrSrrrrrcloserrs r/rzConnectionGuard._on_timeoutsx# ? " F I J N N      &*_d" 4? r1) rArBrCrrrrrrrrrHr1r/rrs(,4        !!!   (((     r1rcPeZdZddddZfdZdefdZdZdZd Z d Z xZ S) _RpcServerProtocolNrc||_||_||_d|_t |_t |||d|_dS)NRPC)rrr)r_sinkrerr_bufr_guard)rrsinkrerrs r/rz_RpcServerProtocol.__init__sL   LL % ' 5    r1c|j|s|dS t|ny#t t tjf$rZ}t d||d|_ |j Yd}~dSd}~wwxYw|j |j|jdS)Nz5Rejected RPC connection: SO_PEERCRED unavailable (%s))rrrsuperconnection_madeOSErrorAttributeErrorstructrTrSrrrr_pid_uid)rrexc __class__s r/rz"_RpcServerProtocol.connection_mades{$$Y//  OO    F  GG # #I . . . .6    NNG    OO   "DO K   ! ! ! FFFFF  di33333s!AC 1ACC rLc~tj|}tj||\}}||_|!||dd<d|dvr |g|dd< t |j}n'#t$r}t|g}Yd}~nd}~wwxYw||d<|S)Nr&rerlcalling_process) r3r4r HostingPanel authenticatererrcmdliner5rX)rrLr+ user_type user_namerr[s r/preprocess_dataz"_RpcServerProtocol.preprocess_datas*T"",9;;HH '   9  (1GH f %'(+++.7[!'* '%di0088::OO ' ' '"1vvhOOOOOO '%4!"s*&B B5B00B5c |jdS|j |j|nx#t $rk}td|j |j ||j d|_|j Yd}~dSd}~wwxYw|jD]} | |}|d}|d}td|t||j}t#j|j } |j||||||j|jt#j|n#t#j|wxYw#t0$rO}td|t4t7|dYd}~:d}~wt8$r]}tdt=||t4t7|dYd}~d}~wwxYwdS)Nz*Closing RPC connection (pid=%s uid=%s): %scommandr&zData received: command=%szIncorrect token providedrMz)Something went wrong before processing %s)rrrrappenddecoderrSrrrrrrrdrmrer!setr create_task _dispatchrresetr_write_responserrXr5 exceptionr:) rrLr[r\rJrZr&rfr#s r/ data_receivedz _RpcServerProtocol.data_received4s ? " F   I  T[[]] + + + +!    NN<       O ! ! # # #"DO K   ! ! ! FFFFF 9$ L$ LC# L--c22 *) 8&AAA&vty99 '*49550J**"FBBvtz49,M,M #(////N(/////( L L L9:::$$3q66%J%JKKKKKKKK L L L  ?%c** $$3q66%J%JKKKKKKKK  L=$ L$ LsX,A CA CCA.F6AFF6F22F66 I4AH  I4AI//I4cRKtjd|5t ||d{V}t d||||ddddS#1swxYwYdS)Nzrpc_{}z Response: method - {}, data - {})rtracktaskr6r]rSinfor)rrZr&rYresponses r/rz_RpcServerProtocol._dispatchks   " "8??6#:#: ; ; + +-dF;;;;;;;;H KK299&(KK      * * *  + + + + + + + + + + + + + + + + + +sABB #B cF|jd|_dSr)rrrrs r/connection_lostz"_RpcServerProtocol.connection_lostts! r1c4|jtddS |jt j|dzdS#t$r%}t|Yd}~dSd}~wwxYw)Nz*Cannot send RPC response: connection lost. ) rrSrwriter3dumpsencoder5r)rrLr[s r/rz"_RpcServerProtocol._write_responsexs ? " NNG H H H F $%%tz$'7'7$'>&F&F&H&HIIIII $ $ $  ######### $sAA(( B2BB) rArBrCrrrXrrrrr __classcell__)rs@r/rrs48t     44444"C*5L5L5Ln+++$$$$$$$r1rctj|}tj|dtj|ddS)NT)exist_oki)ospathdirnamemakedirschmod)rndir_names r/ _check_socket_folder_permissionsrsBw{++HK4((((HXur1cDeZdZejZejZdZe dZ dS) RpcServericKtjtt5t jjdddn #1swxYwYt tj fdjd{V}t j jj |S)NcJtjtjSNr)rUSERConfig READ_TIMEOUTclsrrrsr/z"RpcServer.create..s+&#0 r1) r SOCKET_PATHrFileNotFoundErrorrunlinkrrMAX_CONCURRENT_CONNECTIONScreate_unix_serverr SOCKET_MODE)rrrserverrs``` @r/createzRpcServer.creates(999 ' ( ( ' ' Ico & & & ' ' ' ' ' ' ' ' ' ' ' ' ' ' '#F$EFF..        O          #/222 sAAAN) rArBrCrrrROOTrr  classmethodrrHr1r/rrsA$K =DK[r1rcDeZdZejZejZeZ e dZ dS) RpcServerAVc~Kd}j}t||dr|tdd}t |}d}|D]} t jd5}|D]?} || jd|krt| j } n@ dddg dddnX#1swxYwY#t$r } | }Yd} ~ d} ~ wwxYwtdd| zjj |tj| tjtjtjz} t'jr"t+t,jfd } nfd } | | d{V}|S) aLooking for socket in /proc/net/unix and check which descriptor corresponded to it by comparing inode $ ls -l /proc/[pid]/fd lrwx------ 1 root root 64 Apr 11 07:20 4 -> socket:[2866765] $ cat /proc/net/unix Num RefCount Protocol Flags Type St Inode Path ffff880054c0a4c0: 00000002 00000000 00010000 0001 01 2866765 /var/run/defence360agent/simple_rpc.sock # noqa ctt5tj|i|cdddS#1swxYwYdS)zReturn empty path on error.N)rrrreadlink)rRkwargss r/ safe_readlinkz)RpcServerAV.create..safe_readlinks'"" 4 4{D3F33 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 42s 377z/var/runz/varNz /proc/self/fdz socket:[{}]z"[{}] Socket {!r} for {} not found.inodecVjtjSr)PROTOCOL_CLASSrrrrsr/rz$RpcServerAV.create..s0c00#0 1r1c<jSr)rr)rrrsr/rz$RpcServerAV.create..s c00dCHr1)sock)rr startswithr7r}rscandirrr6intrrrrsocketfromfdAF_UNIX SOCK_STREAM SOCK_NONBLOCKrrrrr r )rrrr _socket_pathinodes last_errorritfd socket_fdr[_socketfactoryr rs``` @r/rzRpcServerAV.creates    (666  " ": . . 7'F 6L!,//   E Z00 B !!(=11]5I5I!66),BG I!E  !                             4;;6z*COSX   -  N  !5 5   $C$6 7 7 '(IJJGGGG..wW.EEEEEEEE sI"C 6AC: C C C C C C C  C6*C11C6N) rArBrCrrrrrrrrrrHr1r/rrsF =D$K'NCC[CCCr1rc*eZdZejZejZdS)NonRootRpcServerAVN) rArBrCrNON_ROOTrrNON_ROOT_SOCKET_PATHrrHr1r/r/r/s  D-KKKr1r/c.eZdZejZejZdZ dS)NonRootRpcServeriN) rArBrCrr1rrr0rr rHr1r/r3r3s%-K  DKKKr1r3c eZdZdZdZdZdS)_RpcClientImplc tjtjtjtjz|_|j|dS#t ttf$rtwxYwr) r!r#r$r%_sockconnectConnectionRefusedErrorrBlockingIOErrorr)rrns r/rz_RpcClientImpl.__init__sy & 2V5I IDJ J  { + + + + +&(9?K & & &#%% % &s AA&A?c |jtj||ddzn$#t $r}t d|d}~wwxYw |d}n%#t$r}td||d}~wwxYw tj | }n5#t$r(}td ||d}~wwxYw|S)Nrr&rzcommunication interrupted,  )terminator_bytezConnection reset: zError parsing RPC response {!r})r7sendallr3rrBrokenPipeErrorr_sock_recv_untilConnectionResetErrorrr4rr5r6)rrZr&r[rLrs r/dispatchz_RpcClientImpl.dispatchsJ A J  J6VDDEEL&((      A A A?A??@@ @ A A(((??DD# A A A 8Q 8 899q @ A z$++--00HH   188>>   sHAA A(A##A(,B B% B  B%)&C D#C==Dc|jrJg}|r ||dvr|jg}tj|ggtj}|d}|j|vr@t |r"td|td|j tj }t|dkrtd| ||||dvd|S)Nrz!select() = {!r} resulted in errorzrequest timeoutz!Empty response from socket.recv()r1)r7 getblockingfilenoselectrCLIENT_TIMEOUTr?rr6recvioDEFAULT_BUFFER_SIZEr7rjoin)rr>chunks fdread_list rwx_fdlist fdready_listchunks r/rAz_RpcClientImpl._sock_recv_untilsG:))+++++ !fRj@@:,,../K% J&a=Lz  "",66z??9%;BB:NN&&7888JOOB$:;;E5zzQ!"EFFF MM% / !fRj@@2xxr1N)rArBrCrrCrArHr1r/r5r5sA&&&.     r1r5ceZdZddZdZdS) _NoRpcImplNc||_ttj5t j}|t|tjddddS#1swxYwYdSr) rrr OverridingResetasyncioget_event_looprun_until_completerr)rrrs r/rz_NoRpcImpl.__init__>s i/ 0 0 L L)++D  # #OD)/$J$J K K K L L L L L L L L L L L L L L L L L LsAA//A36A3ctj}td||||d}t jtj} t|tj }| t|||j|t j|S#t j|wxYw)NzExecuting {}, params: {}r<)re)rWrXrSrr6r!rrgetuidrmrrrYr]rr)rrZr&rrequestr#rfs r/rCz_NoRpcImpl.dispatchJs%'' .55ffEEFFF$77"29;;// ("6 >>>B** GTZ!8!8&AA   ' ' ' 'N  ' ' ' 's -A CC$r)rArBrCrrCrHr1r/rTrT=s; L L L L ( ( ( ( (r1rTcDeZdZdZejddddZdZdZdZ d Z dS) RpcClientaR One RpcClient instance is suitable to use for multiple ipc calls :param RpcServiceState require_svc_is_running: whether to provide direct endpoints binding if the service is stopped. :param int reconnect_with_timeout: timeout in sec for reconnect retries :param int num_retries: number of reconnect retries Nr)require_svc_is_runningreconnect_with_timeout num_retriescd|_tr tjn tj|_|t jkr'trtt j |t j kr+ttj tj|t jt j fvr[ |r||||_nt%|j|_dS#t$r|t j krYnwxYw|j-ts Jdt'|_dSdS)Nz-_NoRpcImpl is not available for non root user)_implrrrr1r&r<rErrrDrractivate_socket_servicer SVC_NAMErF_reconnect_with_timeoutr5rT)rr_r`ras r/rzRpcClient.__init__cse ~~ -F  ,  #o&= = =   >$O$;<< < #'> > > V3DMBB C C C !    #&    )C!%!=!=. ""DJJ"00A!B!BDJ$   )_-DDDED  :  ? ?> ? ?#DJJJ  s7C99DDc6tj|j|Sr functoolspartialr)rrZs r/ __getattr__zRpcClient.__getattr__s 888r1c6tj|j|Srrh)rrs r/cmdz RpcClient.cmds 999r1c h|j||}t|ttfrI|dt t fvr|d|dfS|dtksJ|d|dfS|dt t fvrt|d|dS)NrJrKrL) rcrCr)r`rUrr rr)rrZr&rs r/rzRpcClient._dispatchs:&&vv66 ftUm , , $!eW%555)8J+???)W4444)8F+;;;!eW%555#HZ$8999F# #r1c t|jS#t$r;|r5td|t j||dz}nYnwxYw^)NTz$Waiting %d second(s) before retry...r)r5r&rrSrtimesleep)rtimeoutras r/rfz!RpcClient._reconnect_with_timeouts  %d&7888$   KK>Jw'''1$KK K   sAAA) rArBrC__doc__r<rDrrkrmrrfrHr1r/r^r^Xs /6# ,&,&,&,&,&\999::: $ $ $     r1r^)YrsrWrirrKr3rrHr!rryrp contextlibrloggingrtypingrpsutilrrVdefence360agent.apirdefence360agent.applicationr defence360agent.contracts.configr r r-defence360agent.feature_management.exceptionsr 'defence360agent.internals.auth_protocolr defence360agent.modelr $defence360agent.model.simplificationrdefence360agent.utilsrrdefence360agent.utils.bufferrrdefence360agent.subsys.panelsr"defence360agent.subsys.panels.baserdefence360agent.subsysr$defence360agent.rpc_tools.exceptionsrrr defence360agent.rpc_tools.lookuprrdefence360agent.rpc_tools.utilsrr"defence360agent.rpc_tools.validaterdefence360agent.rpc_toolsrrr r!rArS frozensetr*r0r:r<r]rmrXr}rrrrrrrr/r3r5rTr^rHr1r/rs+ ******++++++FFFFFFFFKJJJJJ++++++@@@@@@88888888GGGGGGGG777777DDDDDD)))))) A@@@@@@@?>>>>> 8  " ">">">??   ***$CCC8   J#J(3-JJJJ<<<,DDDDDDDDN}$}$}$}$}$/}$}$}$@ 2IIIIIIIIX........ y> > > > > > > > B((((((((6ZZZZZZZZZZr1defence360agent/simple_rpc/__pycache__/advisor.cpython-311.opt-1.pyc0000644000000000000000000000625400000000000022232 0ustar r_jvddlmZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z Gdd eZd S) ) defaultdict) ConfigFile) RootEndpoints) update_config)lookup) EventsAPI)config_cleanupceZdZejdddZejdddZdZedZ dS) AdvisorEndpointsadvisorapplyc<K||d{VSN)_applyselfadvicess W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/advisor.py advisor_applyzAdvisorEndpoints.advisor_apply s*[[)))))))))z apply-allcnKtjd{V}||d{VSr)rrrrs r apply_allzAdvisorEndpoints.apply_allsJ!)++++++++[[)))))))))rc:Ktt}t}|D]}||||t |j|d{VdttiS)Nitems)rdictrconfig_to_dict_extract_conf_from_adviser_sinkr )rr target_conf current_confadvises rrzAdvisorEndpoints._applys!$'' !||2244  N NF  * *6< M M M MDJ 444444444 (C(C(E(EFFGGrc|dD]3\}}|D]\}}||||vrdS4|dD] \}}|||!dS)Nignore config_action)rupdate)r!r r section_key section_value value_keyignored_valuess rrz*AdvisorEndpoints._extract_conf_from_advises*0*:*@*@*B*B   &K-:-@-@-B-B  ) > ,Y7>IIFFFJ +1*A*G*G*I*I ; ; &K  $ + +M : : : : ; ;rN) __name__ __module__ __qualname__rbindrrr staticmethodrrrr r sV[G$$**%$*V[K((**)(*HHH;;\;;;rr N) collectionsr defence360agent.contracts.configr defence360agent.rpc_tools.lookuprdefence360agent.utils.configrdefence360agent.rpc_toolsr!defence360agent.api.server.eventsr+defence360agent.feature_management.checkersr r r/rrr7s######777777::::::666666,,,,,,777777FFFFFF;;;;;};;;;;rdefence360agent/simple_rpc/__pycache__/advisor.cpython-311.pyc0000644000000000000000000000625400000000000021273 0ustar r_jvddlmZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z Gdd eZd S) ) defaultdict) ConfigFile) RootEndpoints) update_config)lookup) EventsAPI)config_cleanupceZdZejdddZejdddZdZedZ dS) AdvisorEndpointsadvisorapplyc<K||d{VSN)_applyselfadvicess W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/advisor.py advisor_applyzAdvisorEndpoints.advisor_apply s*[[)))))))))z apply-allcnKtjd{V}||d{VSr)rrrrs r apply_allzAdvisorEndpoints.apply_allsJ!)++++++++[[)))))))))rc:Ktt}t}|D]}||||t |j|d{VdttiS)Nitems)rdictrconfig_to_dict_extract_conf_from_adviser_sinkr )rr target_conf current_confadvises rrzAdvisorEndpoints._applys!$'' !||2244  N NF  * *6< M M M MDJ 444444444 (C(C(E(EFFGGrc|dD]3\}}|D]\}}||||vrdS4|dD] \}}|||!dS)Nignore config_action)rupdate)r!r r section_key section_value value_keyignored_valuess rrz*AdvisorEndpoints._extract_conf_from_advises*0*:*@*@*B*B   &K-:-@-@-B-B  ) > ,Y7>IIFFFJ +1*A*G*G*I*I ; ; &K  $ + +M : : : : ; ;rN) __name__ __module__ __qualname__rbindrrr staticmethodrrrr r sV[G$$**%$*V[K((**)(*HHH;;\;;;rr N) collectionsr defence360agent.contracts.configr defence360agent.rpc_tools.lookuprdefence360agent.utils.configrdefence360agent.rpc_toolsr!defence360agent.api.server.eventsr+defence360agent.feature_management.checkersr r r/rrr7s######777777::::::666666,,,,,,777777FFFFFF;;;;;};;;;;rdefence360agent/simple_rpc/__pycache__/analyst_cleanup.cpython-311.opt-1.pyc0000644000000000000000000002460500000000000023745 0ustar r_j#,ddlZddlmZddlmZmZddlmZddlmZm Z ddl m cm cm ZddlmZmZmZddlmZddlmZmZeeZd Zd Zd Zd Zd ZdeddeedddddddeiZededZ dZ!e"ddehZ#dZ$dZ%GddeZ&dS)N) getLogger)datetime timedelta)ValidationError) RootEndpointsbind) get_ssh_portcheck_ssh_connectioninstall_pub_key)AnalystCleanupRequest)NO_AGENT_TOKENAnalystCleanupAPIzhttps://cloudlinux.zendesk.com/hc/en-us/articles/6245743410460-How-to-authenticate-your-server-for-Support-Team-and-use-the-SSH-access-formz9https://cloudlinux.zendesk.com/auth/v2/login/registrationzeYou are not authorized to submit Analyst Cleanup requests. Contact sales@cloudlinux.com to get accesszxThis server could not authenticate with the Imunify360 API. Make sure the agent is registered and its license is active.zoOur support system returned an unexpected response. Check your email for a ticket confirmation before retrying.not_allowlistednot_authorizedzjThis server is not linked to a CloudLinux customer account. Make sure its license is active and try again.zendesk_unreachablezQOur support system is temporarily unreachable. Please try again in a few minutes.zendesk_upstream_errorzKOur support system rejected the request. Please try again in a few minutes.zendesk_suspendedzZOur support system did not accept the request. Please contact CloudLinux support directly.zendesk_unknown_responsezNThe cleanup request was rejected as invalid. Try again with a shorter message.)izFailed to create support ticketct|dt|tS)Nmessage)_TICKET_ERROR_MESSAGESget _TICKET_ERROR_MESSAGES_BY_STATUS_TICKET_ERROR_DEFAULTstatusbodys _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/analyst_cleanup.py_ticket_error_messager!Ss= ! % % (,,V5JKK  cf|dtvrdS|duod|cxkodkncS)NrTri)r_CLIENT_STATE_CODESrs r _is_expected_client_stater%ZsK xx 111t   5#"5"5"5"5#"5"5"5"55r"ceZdZdeeffdZedddZeddd d Zedd d ZdS)AnalystCleanupEndpointsreturncKtj|||d{V\}}|dpi}|dkrj|drU|dr@td|d|dt |dfSt ||r tjn tj}|d||tt||)z Creates a Zendesk ticket and return link and id of the ticket On any error raises ValidationError, which would be added to RPC answer NticketrurlidzCreated ticket on url z2Failed to create support ticket: status=%s body=%s) r create_ticketrloggerinfostrr%warningerrorrr!)selfemailsubjectfull_descriptionrrr*logs r _create_zendesk_ticketz.AnalystCleanupEndpoints._create_zendesk_ticketas/<            (##)r S==VZZ..=6::d3C3C= KK@@@ A A A%=#fTl"3"33 3)66 FNN  @&$OOO3FDAABBBr"zanalyst-cleanuprequestcKtj|x}rtd|tjd{Vstt tj|d{V}|dds.t|dddtd|d drtj d t|d{V}td{V}t|d{V}d } tjd |d |} d|d| d|d} |s)tj dt"| dt$dz } n!|stj dt"| dz } ||| | d{V\} } tj|| | dd| iiS)zHandle analyst cleanup requestzYou already have an active request for cleaning this user. If you have additional information, you may follow the link and provide new data here: NresultFrzd Couldn't register your email in our Zendesk system. You can make it manually by following the link z( and then try sending the request again.is_newuWe’ve set up a Zendesk account for you! To complete your registration, check your email and click the “Reset Password” button.zAnalyst Cleanup Request:/z Username: z Server Access: z Customer Message: z z'Support SSH public key is not installedz] WARNING: Not able to install analyst's public key Please make it manually by reffering to z+ and provide credentials to zendesk ticketzSSH connection test failedze WARNING: SSH connection test failed. Please verify SSH access and refer to the access request form.)username zendesk_id ticket_linkitems ticket_url)r get_active_request_linkrrcheck_cleanup_allowedNOT_ALLOWLISTED_MESSAGEcheck_registeredrZENDESK_REGISTRATION_URLwarningswarnr r r hp HostingPanel get_server_ipWarningPREPARE_SERVER_GUIDEr8create_request)r3r4r@r active_ticket email_status key_installedssh_port connection_okr5 server_accessr6rD ticket_ids r request_cleanupz'AnalystCleanupEndpoints.request_cleanup}s2I    = "H8EHH  (=???????? ;!"9:: :.?FFFFFFFF %00 !##Ir2222*B222    He , ,  M'   .h77777777 &''''''28<<<<<<<< ,  ..00 H H8 H Hh H H  0 0 0+ 0 0") 0 0 0    MCW M M M $($$$      M6 @ @ @ @  '+&A&A   ' ' ! ! ! ! ! !  I , "    , 344r"z get-requestsN2rcnK|tj||}ntj|||}|rt|dkrgSt jt dz tdfd|D}td||S)z Get status of analyst cleanup requests for all or a specific user Completed tickets will only be visible for 2 weeks after their last update Nr)weekszShowing requests since c g|]}}|jdks |jk|j|j|jt t j|jt t j|j|jd~S) completed)r@rDr created_at last_updaterA) r last_updatedr@rBr0r timestampr`rA).0req two_weeks_agos r z:AnalystCleanupEndpoints.request_status..s    z[((C,<},L,L L!o*!("4S^"D"DEE"8#5c6F#G#GHH!n   -M,L,Lr"zGot requests: ) r get_all_requestsget_user_requestslenrutcnowrr.r/)r3r@limitoffsetrequestsfiltered_requestsrfs @r request_statusz&AnalystCleanupEndpoints.request_statuss  ,=eVLLHH,>%H  3x==A--I!))IA,>,>,>>  =m==>>>            8%688999  r"z is-allowedcDKtjd{V}dd|iiS)NrC is_allowed)rrF)r3rrs r rrz"AnalystCleanupEndpoints.is_alloweds6,BDDDDDDDD , 344r")NrZr) __name__ __module__ __qualname__r0r8rrYrprrr"r r'r'`sC s CCCC8 T Y''M5M5('M5^ T ^,,&!&!&!-,&!P T \**55+*555r"r')'rJloggingrrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrLdefence360agent.utils.sshutilr r r %defence360agent.model.analyst_cleanupr *defence360agent.api.server.analyst_cleanupr rrsr.rPrIrG_NOT_AUTHENTICATED_MESSAGE_UNKNOWN_RESPONSE_MESSAGErrr frozensetr$r!r%r'rvr"r rs((((((((555555@@@@@@@@888888888888 HGGGGG 8  e? 2 D C . :. . . 7 9'. # - $ $$ : i(.9 666 Y5Y5Y5Y5Y5mY5Y5Y5Y5Y5r"defence360agent/simple_rpc/__pycache__/analyst_cleanup.cpython-311.pyc0000644000000000000000000002460500000000000023006 0ustar r_j#,ddlZddlmZddlmZmZddlmZddlmZm Z ddl m cm cm ZddlmZmZmZddlmZddlmZmZeeZd Zd Zd Zd Zd ZdeddeedddddddeiZededZ dZ!e"ddehZ#dZ$dZ%GddeZ&dS)N) getLogger)datetime timedelta)ValidationError) RootEndpointsbind) get_ssh_portcheck_ssh_connectioninstall_pub_key)AnalystCleanupRequest)NO_AGENT_TOKENAnalystCleanupAPIzhttps://cloudlinux.zendesk.com/hc/en-us/articles/6245743410460-How-to-authenticate-your-server-for-Support-Team-and-use-the-SSH-access-formz9https://cloudlinux.zendesk.com/auth/v2/login/registrationzeYou are not authorized to submit Analyst Cleanup requests. Contact sales@cloudlinux.com to get accesszxThis server could not authenticate with the Imunify360 API. Make sure the agent is registered and its license is active.zoOur support system returned an unexpected response. Check your email for a ticket confirmation before retrying.not_allowlistednot_authorizedzjThis server is not linked to a CloudLinux customer account. Make sure its license is active and try again.zendesk_unreachablezQOur support system is temporarily unreachable. Please try again in a few minutes.zendesk_upstream_errorzKOur support system rejected the request. Please try again in a few minutes.zendesk_suspendedzZOur support system did not accept the request. Please contact CloudLinux support directly.zendesk_unknown_responsezNThe cleanup request was rejected as invalid. Try again with a shorter message.)izFailed to create support ticketct|dt|tS)Nmessage)_TICKET_ERROR_MESSAGESget _TICKET_ERROR_MESSAGES_BY_STATUS_TICKET_ERROR_DEFAULTstatusbodys _/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/analyst_cleanup.py_ticket_error_messager!Ss= ! % % (,,V5JKK  cf|dtvrdS|duod|cxkodkncS)NrTri)r_CLIENT_STATE_CODESrs r _is_expected_client_stater%ZsK xx 111t   5#"5"5"5"5#"5"5"5"55r"ceZdZdeeffdZedddZeddd d Zedd d ZdS)AnalystCleanupEndpointsreturncKtj|||d{V\}}|dpi}|dkrj|drU|dr@td|d|dt |dfSt ||r tjn tj}|d||tt||)z Creates a Zendesk ticket and return link and id of the ticket On any error raises ValidationError, which would be added to RPC answer NticketrurlidzCreated ticket on url z2Failed to create support ticket: status=%s body=%s) r create_ticketrloggerinfostrr%warningerrorrr!)selfemailsubjectfull_descriptionrrr*logs r _create_zendesk_ticketz.AnalystCleanupEndpoints._create_zendesk_ticketas/<            (##)r S==VZZ..=6::d3C3C= KK@@@ A A A%=#fTl"3"33 3)66 FNN  @&$OOO3FDAABBBr"zanalyst-cleanuprequestcKtj|x}rtd|tjd{Vstt tj|d{V}|dds.t|dddtd|d drtj d t|d{V}td{V}t|d{V}d } tjd |d |} d|d| d|d} |s)tj dt"| dt$dz } n!|stj dt"| dz } ||| | d{V\} } tj|| | dd| iiS)zHandle analyst cleanup requestzYou already have an active request for cleaning this user. If you have additional information, you may follow the link and provide new data here: NresultFrzd Couldn't register your email in our Zendesk system. You can make it manually by following the link z( and then try sending the request again.is_newuWe’ve set up a Zendesk account for you! To complete your registration, check your email and click the “Reset Password” button.zAnalyst Cleanup Request:/z Username: z Server Access: z Customer Message: z z'Support SSH public key is not installedz] WARNING: Not able to install analyst's public key Please make it manually by reffering to z+ and provide credentials to zendesk ticketzSSH connection test failedze WARNING: SSH connection test failed. Please verify SSH access and refer to the access request form.)username zendesk_id ticket_linkitems ticket_url)r get_active_request_linkrrcheck_cleanup_allowedNOT_ALLOWLISTED_MESSAGEcheck_registeredrZENDESK_REGISTRATION_URLwarningswarnr r r hp HostingPanel get_server_ipWarningPREPARE_SERVER_GUIDEr8create_request)r3r4r@r active_ticket email_status key_installedssh_port connection_okr5 server_accessr6rD ticket_ids r request_cleanupz'AnalystCleanupEndpoints.request_cleanup}s2I    = "H8EHH  (=???????? ;!"9:: :.?FFFFFFFF %00 !##Ir2222*B222    He , ,  M'   .h77777777 &''''''28<<<<<<<< ,  ..00 H H8 H Hh H H  0 0 0+ 0 0") 0 0 0    MCW M M M $($$$      M6 @ @ @ @  '+&A&A   ' ' ! ! ! ! ! !  I , "    , 344r"z get-requestsN2rcnK|tj||}ntj|||}|rt|dkrgSt jt dz tdfd|D}td||S)z Get status of analyst cleanup requests for all or a specific user Completed tickets will only be visible for 2 weeks after their last update Nr)weekszShowing requests since c g|]}}|jdks |jk|j|j|jt t j|jt t j|j|jd~S) completed)r@rDr created_at last_updaterA) r last_updatedr@rBr0r timestampr`rA).0req two_weeks_agos r z:AnalystCleanupEndpoints.request_status..s    z[((C,<},L,L L!o*!("4S^"D"DEE"8#5c6F#G#GHH!n   -M,L,Lr"zGot requests: ) r get_all_requestsget_user_requestslenrutcnowrr.r/)r3r@limitoffsetrequestsfiltered_requestsrfs @r request_statusz&AnalystCleanupEndpoints.request_statuss  ,=eVLLHH,>%H  3x==A--I!))IA,>,>,>>  =m==>>>            8%688999  r"z is-allowedcDKtjd{V}dd|iiS)NrC is_allowed)rrF)r3rrs r rrz"AnalystCleanupEndpoints.is_alloweds6,BDDDDDDDD , 344r")NrZr) __name__ __module__ __qualname__r0r8rrYrprrr"r r'r'`sC s CCCC8 T Y''M5M5('M5^ T ^,,&!&!&!-,&!P T \**55+*555r"r')'rJloggingrrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrLdefence360agent.utils.sshutilr r r %defence360agent.model.analyst_cleanupr *defence360agent.api.server.analyst_cleanupr rrsr.rPrIrG_NOT_AUTHENTICATED_MESSAGE_UNKNOWN_RESPONSE_MESSAGErrr frozensetr$r!r%r'rvr"r rs((((((((555555@@@@@@@@888888888888 HGGGGG 8  e? 2 D C . :. . . 7 9'. # - $ $$ : i(.9 666 Y5Y5Y5Y5Y5mY5Y5Y5Y5Y5r"defence360agent/simple_rpc/__pycache__/endpoints.cpython-311.opt-1.pyc0000644000000000000000000006234600000000000022572 0ustar r_j:2UdZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z ddlmZdd lmZdd lmZmZdd lmZmZmZmZmZmZmZdd lmZdd l m!Z!m"Z"m#Z#ddl$m%Z%m&Z&ddl'm(Z(ddl)m*Z*m+Z+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2m3Z3m4Z4m5Z5m6Z6ddl7m8Z8ddl9m:Z:m;Z;ddlm?Z?ddl@mAZAeeBZCGdde*ZDdZEdZFdZGiZHe eIefeJd<edd ZKdZLdZMiZNe eOefeJd!<d"eId#ePd$eQfd%ZRd#ePd$dfd&ZSd"eId#ePd$dfd'ZTd"eId$dfd(ZUd)eOd#ePd$eQfd*ZVd#ePd$dfd+ZWd)eOd#ePd$dfd,ZXGd-d.e*ZYGd/d0e+ZZGd1d2e*Z[Gd3d4e+Z\Gd5d6e+Z]Gd7d8e+Z^dS)9z" Here you enumerate rpc endpoints N)deque) getLogger)Dict)files) JWTIssuer)NewsFeed)PamAuth)configeula)ANTIVIRUS_MODECoreImmutableMerger LocalConfig MutableMergereffective_user_configint_from_envvar) LicenseCLN)CLNCLNErrorInvalidLicenseError)!collect_billing_incompatibilitiesget_license_type)ValidationError)CommonEndpoints RootEndpointsbind)caller_uid_var)PanelException)IMUNIFY_PACKAGE_NAMES CheckRunErrorcheck_dbgetpwnamsystem_packages_info) update_config)ZendeskAPIError send_request)sync_billing_dataget_doctor_key) hosting_panelceZdZeddddZeddddZeddddZedd dd Zedd dd Zedd ddZ dS)ConfigEndpointsr showNcKtj}|r&t|tj|}d|iSd|iSNitems)r ConfigFilerconfig_to_dict)selfuser full_confuser_conf_dicts Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/endpoints.py config_showzConfigEndpoints.config_show;s_%''  926,T22N^, ,Y55778 8defaultscKtj}dt|tdt |diS)Nr0F) normalize)mutable_config local_configimmutable_config)rget_layer_namesconfigs_to_dictrr2r)r3 layer_pathss r7config_show_defaultsz$ConfigEndpoints.config_show_defaultsFsu#355 "/ "<"<"L"L"N"N + < z$_login_pam_sweep..s& I I I41a!B%&..Q...r9)rfr^r0)rbstalerarks @r7_login_pam_sweeprwsX $ $F I I I I.4466 I I IE**  ) )**r9cl|tvrptttkrSt|tttkr'tt t t=t|t|dSrW) r^rh_LOGIN_PAM_MAX_TRACKEDrwnextiter setdefaultrappend)rarbs r7_login_pam_record_failurer~s+++ # $ $(> > > " # #'= = =#D.A)B)B$C$CD""8UWW55<.s& M M M41aaefnnQnnnr9)rr`r0)rbrvrrks @r7_login_pam_uid_sweeprsX ( (F M M M M288:: M M ME)) #C ( ())r9ctdkrdS|tvrptttkrSt |tttkr'tt t t=t|t |dS)Nr) rr`rh_LOGIN_PAM_UID_MAX_TRACKEDrrzr{r|rr})rrbs r7_login_pam_uid_record_failurersQ *** ' ( (,F F FS!!! & ' '+E E E'T2I-J-J(K(KL&&sEGG44;;C@@@@@r9c8eZdZedddZdS)LoginEndpointsloginpamcrKtj} tj}n7#t$r*t dtdwxYw|dkr:t||s*t d|tdt||s*t d|tdt}| ||}|sPt|||dkrt||t d|tdt!|t d |d t%|||d{ViS) Nz.AUDIT login.pam REJECTED: caller_uid_var unsetz,login.pam reached without caller_uid_var setrz#AUDIT login.pam RATE_LIMITED uid=%rz"Authentication rate limit exceededz(AUDIT login.pam RATE_LIMITED username=%rz"AUDIT login.pam FAILED username=%rzAuthentication failedz#AUDIT login.pam SUCCESS username=%rr0)time monotonicrre LookupErrorrHerror RuntimeErrorrrIrrlr authenticater~rrinfor get_token get_user_type)r3rapasswordrb caller_uidpam_auth authenticateds r7 login_via_pamzLoginEndpoints.login_via_pamsn O'+--JJ O O O LLI J J JMNN N O ??#9*c#J#J? NN@* M M M!"FGG G!(C00 H NN:H   ""FGG G99 --hAA  ; %h 4 4 4Q-j#>>> NN? J J J!"9:: :""" 98DDD Y[[** 6 6x @ @@@@@@@  s +4AN)rYrZr[rrr\r9r7rrs: T'5     r9rc8eZdZedddZdS)RootLoginEndpointsrrecKt|stddt|t |d{ViS)NzUser name not foundr0)r"rrrr r)r3ras r7 login_getzRootLoginEndpoints.login_getss!! 9!"788 8 Y[[** 7 7 A AAAAAAA  r9N)rYrZr[rrr\r9r7rrs: T'5     r9rc8eZdZedddZdS)PackageVersionsEndpointszget-package-versionsNc>Kdttd{ViSr/)r#r)r3r4s r7get_package_versionsz-PackageVersionsEndpoints.get_package_versionss-34IJJJJJJJJKKr9rW)rYrZr[rrr\r9r7rrsD T !!LLL"!LLLr9rc6eZdZeddZdS) NewsEndpointszget-newsc<Kdtjd{ViSr/)rrer3s r7get_newszNewsEndpoints.get_news s)x|~~------..r9N)rYrZr[rrr\r9r7rrs8 T*/////r9rceZdZejZedddZeddZeddZeddd Z ed d Z ed dZ ed d dZ edddZ edddZedddZeddZedd d!dZdS)" EndpointsregisterNc HKtjtjrrtjrt st dnHtdtjz| d{V tj |d{Vn_#t$r!}t t|d}~wt$r(}td| tj t!jd{Vd{Vn#t&$r7tdt t|t($rB}t dt|t|d}~wttf$r!}t t|d}~wwxYwYd}~nd}~wwxYwiS)NzAgent is already registeredz!Unregistering invalid license: %szUCan't register %r as imunify360 key. Trying to register it as a web panel key insteadz3Registration with web panel's key doesn't supportedz{}, {})rr cache_clear is_registeredis_validr rrHr unregisterrrrstrrrIr* HostingPanel retrieve_keyNotImplementedErrorrformat)r3regkeyepanel_es r7rzEndpoints.registers((***  # % % ("$$ (%I)*GHHHI 7 *,,-oo''''''''' .,v&& & & & & & & & &" * * *!#a&&)) ) . . . NN9    .l'466CCEEEEEEEE' . . .I&c!ff---! M M M%hooc!ffc'll&K&KLLL12 . . .%c!ff--- . .& sV'C H C))H7HAEHA H =GH1H  HHHrcKtjstdtjrtdt jd{ViS)NzAgent is not registered yetz$Free license can not be unregistered)rrris_freerrrs r7rzEndpoints.unregister7sm')) A!"?@@ @     J!"HII In r9zupdate-licensec4Ktjstdtj}t jd{Vt_tj|d{V}|tdiS)Nz(Unregistered (server-id is not assigned)z*License does not exist. Agent unregistered) rrrrr*r users_countr refresh_token)r3token new_tokens r7update_licensezEndpoints.update_licenseAs')) N!"LMM M$&&,..::<< < < < < < < +E22222222  !"NOO O r9rstatusFcKtjtjst d|r"tjrt d|S)Nz%License is invalid for current serverz Free license)rrrrrr license_info)r3paids r7rzEndpoints.rstatusNst((***"$$ K!"IJJ J  2J&(( 2!.11 1  """r9versionc"KdtjiSr/) CoreConfigVERSIONrs r7rzEndpoints.versionWs+,,r9wakeupc KiS)zBWake up the agent, so it can process the request, if it's sleepingr\rs r7rzEndpoints.wakeup[s  r9rDlatestcK|rl|tjjvrY|r&tj|S|r.tj|||d{VSn|s|dkrtd tj||d{VdS#tj tj f$rYdSwxYw)Nrz9Listing and version are not supported for this files type) r FilesUpdateDISABLEDrIndexget_list update_torrDasyncio TimeoutError UpdateError)r3subjforcelistrs r7 update_fileszEndpoints.update_files`s   DF.777 4{4((11333 I"[..88%HHHHHHHHH I w(**%O ,tU++ + + + + + + + + +$e&78    DD s B&&CCr acceptc<Ktjd{VdSrW)r rrs r7 eula_acceptzEndpoints.eula_acceptss*kmmr9r-c,KtjSrW)r textrs r7 eula_showzEndpoints.eula_showwsy{{r9checkdbc^K|rtjdStjdS)zmCheck DB consistency and repair if needed. If recreate_schema is set recreate schema for attached DB.N)r!recreate_schemacheck_and_repair)r3rs r7rzEndpoints.checkdb{s:  (  $ & & & & &  % ' ' ' ' 'r9doctorc8Ktd{V}d|zS)Nz8Please, provide this key: %s to Imunify360 Support Team r()r3keys r7rzEndpoints.doctors0"$$$$$$$$ IC O r9supportsendcK td{V}n#t$rd}YnwxYw t||||||d{V}n?#t$r2}td|j|j|jd}~wwxYwd|giS)Nz@Got error from Zendesk API. error=%s, description=%s, details=%sr0)r)r r&r%rHr descriptiondetails) r3emailsubjectrcln attachments doctor_key ticket_urlrs r7send_to_supportzEndpoints.send_to_supports  -////////JJ   JJJ  +w Zk  JJ    LL       *&&s# ((A B-A>>BrW)F)NFFrrX)rYrZr[rrrrrrrrrrrrrrrr\r9r7rrs*L T*####J T, T     T)__###_# T)__--_- T(^^^ T(^^:B^$ T&( T&& T)__(((_( T(^^  ^  T)VAE''''''r9rcjeZdZdZdZedddZedddZdS) WhmcsEndpointz< Describes all endpoints for interaction with WHMCS 1billingsynccK tj|}n"#tj$rtdwxYwt |j|d{V}d|dS)Nz Invalid JSONsuccessrUrK)rFrGJSONDecodeError ValueErrorr'rJ)r3rK decoded_datarUs r7 billing_synczWhmcsEndpoint.billing_syncsy -:d++LL# - - -^,, , -(\BBBBBBBB#V444s8z get-configczKt|jttd{V}d|dS)N)rbilling_licenseissuesrr)dictrrr)r3rUs r7billing_get_configz WhmcsEndpoint.billing_get_configsSL,..:<<<<<<<<    $V444r9N)rYrZr[__doc__rrrrr\r9r7rrss G T)V555 T)\""55#"555r9r)_rrrFr collectionsrloggingrtypingrdefence360agentrdefence360agent.api.jwt_issuerrdefence360agent.api.newsfeedrdefence360agent.api.pam_authr defence360agent.contractsr r defence360agent.contracts.configr r rrrrrr!defence360agent.contracts.licenserdefence360agent.internals.clnrrr!defence360agent.myimunify.billingrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrrdefence360agent.simple_rpcr"defence360agent.subsys.panels.baserdefence360agent.utilsrr r!r"r#defence360agent.utils.configr$defence360agent.utils.supportr%r&defence360agent.utils.whmcsr'defence360agent.utils.doctorr)defence360agent.subsys.panelsr*rYrHr,rirfryr^r__annotations__rrrr`intfloatboolrlrwr~rrrrrrrrrrr\r9r7r"sg !!!!!!44444411111100000022222222988888LLLLLLLLLL655555 655555======766666GGGGGGGG999999777777777777 8  BBBBBoBBBL(*T#u*%***$_%=sCC#,.c5j)... ) )5 )T ) ) ) )*%*D****BB%BDBBBB,s,t,,,, - -% -D - - - -)e))))) As A A4 A A A A! ! ! ! ! _! ! ! H          LLLLLLLL /////M/// W'W'W'W'W' W'W'W't55555M55555r9defence360agent/simple_rpc/__pycache__/endpoints.cpython-311.pyc0000644000000000000000000006234600000000000021633 0ustar r_j:2UdZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z ddlmZdd lmZdd lmZmZdd lmZmZmZmZmZmZmZdd lmZdd l m!Z!m"Z"m#Z#ddl$m%Z%m&Z&ddl'm(Z(ddl)m*Z*m+Z+m,Z,ddl-m.Z.ddl/m0Z0ddl1m2Z2m3Z3m4Z4m5Z5m6Z6ddl7m8Z8ddl9m:Z:m;Z;ddlm?Z?ddl@mAZAeeBZCGdde*ZDdZEdZFdZGiZHe eIefeJd<edd ZKdZLdZMiZNe eOefeJd!<d"eId#ePd$eQfd%ZRd#ePd$dfd&ZSd"eId#ePd$dfd'ZTd"eId$dfd(ZUd)eOd#ePd$eQfd*ZVd#ePd$dfd+ZWd)eOd#ePd$dfd,ZXGd-d.e*ZYGd/d0e+ZZGd1d2e*Z[Gd3d4e+Z\Gd5d6e+Z]Gd7d8e+Z^dS)9z" Here you enumerate rpc endpoints N)deque) getLogger)Dict)files) JWTIssuer)NewsFeed)PamAuth)configeula)ANTIVIRUS_MODECoreImmutableMerger LocalConfig MutableMergereffective_user_configint_from_envvar) LicenseCLN)CLNCLNErrorInvalidLicenseError)!collect_billing_incompatibilitiesget_license_type)ValidationError)CommonEndpoints RootEndpointsbind)caller_uid_var)PanelException)IMUNIFY_PACKAGE_NAMES CheckRunErrorcheck_dbgetpwnamsystem_packages_info) update_config)ZendeskAPIError send_request)sync_billing_dataget_doctor_key) hosting_panelceZdZeddddZeddddZeddddZedd dd Zedd dd Zedd ddZ dS)ConfigEndpointsr showNcKtj}|r&t|tj|}d|iSd|iSNitems)r ConfigFilerconfig_to_dict)selfuser full_confuser_conf_dicts Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/endpoints.py config_showzConfigEndpoints.config_show;s_%''  926,T22N^, ,Y55778 8defaultscKtj}dt|tdt |diS)Nr0F) normalize)mutable_config local_configimmutable_config)rget_layer_namesconfigs_to_dictrr2r)r3 layer_pathss r7config_show_defaultsz$ConfigEndpoints.config_show_defaultsFsu#355 "/ "<"<"L"L"N"N + < z$_login_pam_sweep..s& I I I41a!B%&..Q...r9)rfr^r0)rbstalerarks @r7_login_pam_sweeprwsX $ $F I I I I.4466 I I IE**  ) )**r9cl|tvrptttkrSt|tttkr'tt t t=t|t|dSrW) r^rh_LOGIN_PAM_MAX_TRACKEDrwnextiter setdefaultrappend)rarbs r7_login_pam_record_failurer~s+++ # $ $(> > > " # #'= = =#D.A)B)B$C$CD""8UWW55<.s& M M M41aaefnnQnnnr9)rr`r0)rbrvrrks @r7_login_pam_uid_sweeprsX ( (F M M M M288:: M M ME)) #C ( ())r9ctdkrdS|tvrptttkrSt |tttkr'tt t t=t|t |dS)Nr) rr`rh_LOGIN_PAM_UID_MAX_TRACKEDrrzr{r|rr})rrbs r7_login_pam_uid_record_failurersQ *** ' ( (,F F FS!!! & ' '+E E E'T2I-J-J(K(KL&&sEGG44;;C@@@@@r9c8eZdZedddZdS)LoginEndpointsloginpamcrKtj} tj}n7#t$r*t dtdwxYw|dkr:t||s*t d|tdt||s*t d|tdt}| ||}|sPt|||dkrt||t d|tdt!|t d |d t%|||d{ViS) Nz.AUDIT login.pam REJECTED: caller_uid_var unsetz,login.pam reached without caller_uid_var setrz#AUDIT login.pam RATE_LIMITED uid=%rz"Authentication rate limit exceededz(AUDIT login.pam RATE_LIMITED username=%rz"AUDIT login.pam FAILED username=%rzAuthentication failedz#AUDIT login.pam SUCCESS username=%rr0)time monotonicrre LookupErrorrHerror RuntimeErrorrrIrrlr authenticater~rrinfor get_token get_user_type)r3rapasswordrb caller_uidpam_auth authenticateds r7 login_via_pamzLoginEndpoints.login_via_pamsn O'+--JJ O O O LLI J J JMNN N O ??#9*c#J#J? NN@* M M M!"FGG G!(C00 H NN:H   ""FGG G99 --hAA  ; %h 4 4 4Q-j#>>> NN? J J J!"9:: :""" 98DDD Y[[** 6 6x @ @@@@@@@  s +4AN)rYrZr[rrr\r9r7rrs: T'5     r9rc8eZdZedddZdS)RootLoginEndpointsrrecKt|stddt|t |d{ViS)NzUser name not foundr0)r"rrrr r)r3ras r7 login_getzRootLoginEndpoints.login_getss!! 9!"788 8 Y[[** 7 7 A AAAAAAA  r9N)rYrZr[rrr\r9r7rrs: T'5     r9rc8eZdZedddZdS)PackageVersionsEndpointszget-package-versionsNc>Kdttd{ViSr/)r#r)r3r4s r7get_package_versionsz-PackageVersionsEndpoints.get_package_versionss-34IJJJJJJJJKKr9rW)rYrZr[rrr\r9r7rrsD T !!LLL"!LLLr9rc6eZdZeddZdS) NewsEndpointszget-newsc<Kdtjd{ViSr/)rrer3s r7get_newszNewsEndpoints.get_news s)x|~~------..r9N)rYrZr[rrr\r9r7rrs8 T*/////r9rceZdZejZedddZeddZeddZeddd Z ed d Z ed dZ ed d dZ edddZ edddZedddZeddZedd d!dZdS)" EndpointsregisterNc HKtjtjrrtjrt st dnHtdtjz| d{V tj |d{Vn_#t$r!}t t|d}~wt$r(}td| tj t!jd{Vd{Vn#t&$r7tdt t|t($rB}t dt|t|d}~wttf$r!}t t|d}~wwxYwYd}~nd}~wwxYwiS)NzAgent is already registeredz!Unregistering invalid license: %szUCan't register %r as imunify360 key. Trying to register it as a web panel key insteadz3Registration with web panel's key doesn't supportedz{}, {})rr cache_clear is_registeredis_validr rrHr unregisterrrrstrrrIr* HostingPanel retrieve_keyNotImplementedErrorrformat)r3regkeyepanel_es r7rzEndpoints.registers((***  # % % ("$$ (%I)*GHHHI 7 *,,-oo''''''''' .,v&& & & & & & & & &" * * *!#a&&)) ) . . . NN9    .l'466CCEEEEEEEE' . . .I&c!ff---! M M M%hooc!ffc'll&K&KLLL12 . . .%c!ff--- . .& sV'C H C))H7HAEHA H =GH1H  HHHrcKtjstdtjrtdt jd{ViS)NzAgent is not registered yetz$Free license can not be unregistered)rrris_freerrrs r7rzEndpoints.unregister7sm')) A!"?@@ @     J!"HII In r9zupdate-licensec4Ktjstdtj}t jd{Vt_tj|d{V}|tdiS)Nz(Unregistered (server-id is not assigned)z*License does not exist. Agent unregistered) rrrrr*r users_countr refresh_token)r3token new_tokens r7update_licensezEndpoints.update_licenseAs')) N!"LMM M$&&,..::<< < < < < < < +E22222222  !"NOO O r9rstatusFcKtjtjst d|r"tjrt d|S)Nz%License is invalid for current serverz Free license)rrrrrr license_info)r3paids r7rzEndpoints.rstatusNst((***"$$ K!"IJJ J  2J&(( 2!.11 1  """r9versionc"KdtjiSr/) CoreConfigVERSIONrs r7rzEndpoints.versionWs+,,r9wakeupc KiS)zBWake up the agent, so it can process the request, if it's sleepingr\rs r7rzEndpoints.wakeup[s  r9rDlatestcK|rl|tjjvrY|r&tj|S|r.tj|||d{VSn|s|dkrtd tj||d{VdS#tj tj f$rYdSwxYw)Nrz9Listing and version are not supported for this files type) r FilesUpdateDISABLEDrIndexget_list update_torrDasyncio TimeoutError UpdateError)r3subjforcelistrs r7 update_fileszEndpoints.update_files`s   DF.777 4{4((11333 I"[..88%HHHHHHHHH I w(**%O ,tU++ + + + + + + + + +$e&78    DD s B&&CCr acceptc<Ktjd{VdSrW)r rrs r7 eula_acceptzEndpoints.eula_acceptss*kmmr9r-c,KtjSrW)r textrs r7 eula_showzEndpoints.eula_showwsy{{r9checkdbc^K|rtjdStjdS)zmCheck DB consistency and repair if needed. If recreate_schema is set recreate schema for attached DB.N)r!recreate_schemacheck_and_repair)r3rs r7rzEndpoints.checkdb{s:  (  $ & & & & &  % ' ' ' ' 'r9doctorc8Ktd{V}d|zS)Nz8Please, provide this key: %s to Imunify360 Support Team r()r3keys r7rzEndpoints.doctors0"$$$$$$$$ IC O r9supportsendcK td{V}n#t$rd}YnwxYw t||||||d{V}n?#t$r2}td|j|j|jd}~wwxYwd|giS)Nz@Got error from Zendesk API. error=%s, description=%s, details=%sr0)r)r r&r%rHr descriptiondetails) r3emailsubjectrcln attachments doctor_key ticket_urlrs r7send_to_supportzEndpoints.send_to_supports  -////////JJ   JJJ  +w Zk  JJ    LL       *&&s# ((A B-A>>BrW)F)NFFrrX)rYrZr[rrrrrrrrrrrrrrrr\r9r7rrs*L T*####J T, T     T)__###_# T)__--_- T(^^^ T(^^:B^$ T&( T&& T)__(((_( T(^^  ^  T)VAE''''''r9rcjeZdZdZdZedddZedddZdS) WhmcsEndpointz< Describes all endpoints for interaction with WHMCS 1billingsynccK tj|}n"#tj$rtdwxYwt |j|d{V}d|dS)Nz Invalid JSONsuccessrUrK)rFrGJSONDecodeError ValueErrorr'rJ)r3rK decoded_datarUs r7 billing_synczWhmcsEndpoint.billing_syncsy -:d++LL# - - -^,, , -(\BBBBBBBB#V444s8z get-configczKt|jttd{V}d|dS)N)rbilling_licenseissuesrr)dictrrr)r3rUs r7billing_get_configz WhmcsEndpoint.billing_get_configsSL,..:<<<<<<<<    $V444r9N)rYrZr[__doc__rrrrr\r9r7rrss G T)V555 T)\""55#"555r9r)_rrrFr collectionsrloggingrtypingrdefence360agentrdefence360agent.api.jwt_issuerrdefence360agent.api.newsfeedrdefence360agent.api.pam_authr defence360agent.contractsr r defence360agent.contracts.configr r rrrrrr!defence360agent.contracts.licenserdefence360agent.internals.clnrrr!defence360agent.myimunify.billingrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrrdefence360agent.simple_rpcr"defence360agent.subsys.panels.baserdefence360agent.utilsrr r!r"r#defence360agent.utils.configr$defence360agent.utils.supportr%r&defence360agent.utils.whmcsr'defence360agent.utils.doctorr)defence360agent.subsys.panelsr*rYrHr,rirfryr^r__annotations__rrrr`intfloatboolrlrwr~rrrrrrrrrrr\r9r7r"sg !!!!!!44444411111100000022222222988888LLLLLLLLLL655555 655555======766666GGGGGGGG999999777777777777 8  BBBBBoBBBL(*T#u*%***$_%=sCC#,.c5j)... ) )5 )T ) ) ) )*%*D****BB%BDBBBB,s,t,,,, - -% -D - - - -)e))))) As A A4 A A A A! ! ! ! ! _! ! ! H          LLLLLLLL /////M/// W'W'W'W'W' W'W'W't55555M55555r9defence360agent/simple_rpc/__pycache__/hooks.cpython-311.opt-1.pyc0000644000000000000000000001260100000000000021677 0ustar r_jH ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZejeZGd d e ZdS) N) HookEvents) HooksConfig) LicenseCLN) EventHook)ValidationError) RootEndpointsbind)notifierc@eZdZddZedddZedddZeddd Zedd d Zed d dZ ed dddZ ed dddZ dS)HooksEndpointsNcv|tjvr(||kr$td|dSdS)Nz "{}" is not valid event for hook)rEVENTSrformat)selfeventextras U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hooks.py _check_eventzHooksEndpoints._check_eventsG  ) ) )eunn!299%@@  * )nnhookaddcK||tj||}|s#td||d|d<d|iS)NrpathzUnable to add hook "{} {}" registeredstatusitemsrradd_hookrrrrrresults rhook_addzHooksEndpoints.hook_addss %   #%d;;; !,33E4@@ (x  rdeletecK||tj||}|s#td||d|d<d|iS)NrzUnable to delete hook "{} {}" unregisteredrr)rr delete_hookrrr s r hook_deletezHooksEndpoints.hook_delete!ss %   &U>>> !/66udCC *x  rlistcbK||dtj|}d|iS)Nallr)rr list_events)rrr!s r hook_listzHooksEndpoints.hook_list,s7 %'''&u--  rz add-nativecK||tj||d}|s#td||d|d<d|iS)NT)rrnativez!Unable to add native hook "{} {}"rrrrr s rhook_add_nativezHooksEndpoints.hook_add_native2su %   #%d4HHH !3::5$GG (x  rznotifications-configshowcJKdtiS)Nr)rget)rs rr0zHooksEndpoints.show=s!**,,--rupdatec.Ktjrtd|r|d}tj|}t |tjd{V| d{VS)N*This action is not allowed in demo versionr) ris_demorjsonloadsrr3r config_updatedr0)rrdatanew_datas rr3zHooksEndpoints.updateAs     P!"NOO O  8D:d## X&&&%'''''''''YY[[       rpatchcKtjrtdt|t jd{V|d{VS)Nr5)rr6rrr3r r9r0)rr:s r update_uizHooksEndpoints.update_uiLs     P!"NOO O T"""%'''''''''YY[[       r)N)NN) __name__ __module__ __qualname__rr r"r'r,r/r0r3r>rrr r sM  T&%!!! T&(!!! T&&!!!  T&,!! ! T &))..*). T (++!!!,+! T '**!!!+*!!!rr )r7logging defence360agent.contracts.configrdefence360agent.contracts.hooksr!defence360agent.contracts.licenser defence360agent.model.event_hookrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprr defence360agent.subsysr getLoggerr?loggerr rBrrrMs 777777777777888888666666555555@@@@@@@@++++++  8 $ $C!C!C!C!C!]C!C!C!C!C!rdefence360agent/simple_rpc/__pycache__/hooks.cpython-311.pyc0000644000000000000000000001260100000000000020740 0ustar r_jH ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZejeZGd d e ZdS) N) HookEvents) HooksConfig) LicenseCLN) EventHook)ValidationError) RootEndpointsbind)notifierc@eZdZddZedddZedddZeddd Zedd d Zed d dZ ed dddZ ed dddZ dS)HooksEndpointsNcv|tjvr(||kr$td|dSdS)Nz "{}" is not valid event for hook)rEVENTSrformat)selfeventextras U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hooks.py _check_eventzHooksEndpoints._check_eventsG  ) ) )eunn!299%@@  * )nnhookaddcK||tj||}|s#td||d|d<d|iS)NrpathzUnable to add hook "{} {}" registeredstatusitemsrradd_hookrrrrrresults rhook_addzHooksEndpoints.hook_addss %   #%d;;; !,33E4@@ (x  rdeletecK||tj||}|s#td||d|d<d|iS)NrzUnable to delete hook "{} {}" unregisteredrr)rr delete_hookrrr s r hook_deletezHooksEndpoints.hook_delete!ss %   &U>>> !/66udCC *x  rlistcbK||dtj|}d|iS)Nallr)rr list_events)rrr!s r hook_listzHooksEndpoints.hook_list,s7 %'''&u--  rz add-nativecK||tj||d}|s#td||d|d<d|iS)NT)rrnativez!Unable to add native hook "{} {}"rrrrr s rhook_add_nativezHooksEndpoints.hook_add_native2su %   #%d4HHH !3::5$GG (x  rznotifications-configshowcJKdtiS)Nr)rget)rs rr0zHooksEndpoints.show=s!**,,--rupdatec.Ktjrtd|r|d}tj|}t |tjd{V| d{VS)N*This action is not allowed in demo versionr) ris_demorjsonloadsrr3r config_updatedr0)rrdatanew_datas rr3zHooksEndpoints.updateAs     P!"NOO O  8D:d## X&&&%'''''''''YY[[       rpatchcKtjrtdt|t jd{V|d{VS)Nr5)rr6rrr3r r9r0)rr:s r update_uizHooksEndpoints.update_uiLs     P!"NOO O T"""%'''''''''YY[[       r)N)NN) __name__ __module__ __qualname__rr r"r'r,r/r0r3r>rrr r sM  T&%!!! T&(!!! T&&!!!  T&,!! ! T &))..*). T (++!!!,+! T '**!!!+*!!!rr )r7logging defence360agent.contracts.configrdefence360agent.contracts.hooksr!defence360agent.contracts.licenser defence360agent.model.event_hookrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprr defence360agent.subsysr getLoggerr?loggerr rBrrrMs 777777777777888888666666555555@@@@@@@@++++++  8 $ $C!C!C!C!C!]C!C!C!C!C!rdefence360agent/simple_rpc/__pycache__/hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000660300000000000023413 0ustar r_jbddlmZddlmZddlmZddlmZddlm Z m Z Gdde Z dS) )PanelException) DirectAdmin) HostingPanel)ValidationError) RootEndpointsbindceZdZedd dZedd dZeddZedd Zed d Ze d Z dS)HostingPanelEndpointsz enable-pluginNcFK|j|d{VSN) hosting_panelenable_imunify_pluginself plugin_names ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hosting_panel.py enable_pluginz#HostingPanelEndpoints.enable_plugin s/'==kJJJJJJJJJzdisable-plugincFK|j|d{VSr )r disable_imunify_pluginrs rdisable_pluginz$HostingPanelEndpoints.disable_plugin s/'>>{KKKKKKKKKrz add-sudousercK|j}t|tstd||d{VSNz&Feature available only for DirectAdmin)r isinstancerr add_sudouserruserhps rrz"HostingPanelEndpoints.add_sudousersT  "k** L!"JKK K__T*********rzdelete-sudousercK|j}t|tstd||d{VSr)r rrrdelete_sudouserrs rr z%HostingPanelEndpoints.delete_sudousersV  "k** L!"JKK K''---------rz list-docrootscHKd|jd{ViS)Nitems)r list_docroots)rs r get_docrootsz"HostingPanelEndpoints.get_docroots!s2t1??AAAAAAAABBrc| tS#t$r!}tt|d}~wwxYwr )rrrstr)res rr z#HostingPanelEndpoints.hosting_panel%sD *>> ! * * *!#a&&)) ) *s  ;6;r ) __name__ __module__ __qualname__rrrrr r$propertyr rrr r s T/KKKK T LLLL T.+++ T ... T/CCC**X***rr N) "defence360agent.subsys.panels.baser)defence360agent.subsys.panels.directadminr+defence360agent.subsys.panels.hosting_panelrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrr r,rrr2s======AAAAAADDDDDD555555@@@@@@@@"*"*"*"*"*M"*"*"*"*"*rdefence360agent/simple_rpc/__pycache__/hosting_panel.cpython-311.pyc0000644000000000000000000000660300000000000022454 0ustar r_jbddlmZddlmZddlmZddlmZddlm Z m Z Gdde Z dS) )PanelException) DirectAdmin) HostingPanel)ValidationError) RootEndpointsbindceZdZedd dZedd dZeddZedd Zed d Ze d Z dS)HostingPanelEndpointsz enable-pluginNcFK|j|d{VSN) hosting_panelenable_imunify_pluginself plugin_names ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/hosting_panel.py enable_pluginz#HostingPanelEndpoints.enable_plugin s/'==kJJJJJJJJJzdisable-plugincFK|j|d{VSr )r disable_imunify_pluginrs rdisable_pluginz$HostingPanelEndpoints.disable_plugin s/'>>{KKKKKKKKKrz add-sudousercK|j}t|tstd||d{VSNz&Feature available only for DirectAdmin)r isinstancerr add_sudouserruserhps rrz"HostingPanelEndpoints.add_sudousersT  "k** L!"JKK K__T*********rzdelete-sudousercK|j}t|tstd||d{VSr)r rrrdelete_sudouserrs rr z%HostingPanelEndpoints.delete_sudousersV  "k** L!"JKK K''---------rz list-docrootscHKd|jd{ViS)Nitems)r list_docroots)rs r get_docrootsz"HostingPanelEndpoints.get_docroots!s2t1??AAAAAAAABBrc| tS#t$r!}tt|d}~wwxYwr )rrrstr)res rr z#HostingPanelEndpoints.hosting_panel%sD *>> ! * * *!#a&&)) ) *s  ;6;r ) __name__ __module__ __qualname__rrrrr r$propertyr rrr r s T/KKKK T LLLL T.+++ T ... T/CCC**X***rr N) "defence360agent.subsys.panels.baser)defence360agent.subsys.panels.directadminr+defence360agent.subsys.panels.hosting_panelrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrr r,rrr2s======AAAAAADDDDDD555555@@@@@@@@"*"*"*"*"*M"*"*"*"*"*rdefence360agent/simple_rpc/__pycache__/myimunify.cpython-311.opt-1.pyc0000644000000000000000000001111500000000000022601 0ustar r_j ddlZddlmZmZddlmcmcmZ ddl m Z m Z ddl mZmZmZddlmZddlmZGddejZGd d ejZdS) N)ListOptional)MyImunifyConfigis_mi_freemium_license) MyImunify#set_protection_status_for_all_usersupdate_users_protection)lookup)ScopeceZdZejZejdddee de fdZ ejdddZ ejddd Z d S) MyImunifyEndpoints myimunifyupdateitems protectioncJKt|j||dkd{ViS)Nenabled)r _sink)selfrrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/myimunify.pyrzMyImunifyEndpoints.updatesK% JzY6          z enable-allc@Kt|jdd{VdS)NTrrrs r enable_allzMyImunifyEndpoints.enable_alls01$*dCCCCCCCCCCCrz disable-allc@Kt|jdd{VdS)NFrrs r disable_allzMyImunifyEndpoints.disable_all s01$*eDDDDDDDDDDDrN) __name__ __module__ __qualname__r IM360SCOPEr bindrstrrrrrrr r s KEV[h''$s)(' V[l++DD,+DV[m,,EE-,EEErr cteZdZejZejddddee de e fdZ dS)MyImunifyCommonEndpointsrstatusNruserc Ktj}tj}||g}tjr|d{V|g}tt|d}tjdztj ddd||| dz}tjtj|}tj|t'd|DdS)Nz/?cloudlinux_advantage provisioningmy_imunify_account_protection)mactionsuiteusernamedomain server_ipc0g|]}|d|ddS)r)r)r1rr%).0items r z3MyImunifyCommonEndpoints.status..Hs9"&\l9KLLr)myimunify_enabledpurchase_page_url is_freemiumr)rPURCHASE_PAGE_URLhp HostingPanelENABLEDget_domains_per_usergetnextiterurllibparse urlencode get_server_iprselectwherer)in_dictsr)rrr) purchase_url panel_manager user_domainsr2responses rr(zMyImunifyCommonEndpoints.status(sM&8 ))  FE& '<<>>>>>>>>#dB--d<00$77#5l,,!7&4%D(,&,)6)D)D)F)F    #%%++IN,>,>u,E,EFFLLNN!0!8!-133$    r)N) rrr r r!r"r r#rr$rr(r%rrr'r'%sc KEV[h''# # $s)# 8C=# # # ('# # # rr') urllib.parserCtypingrr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr< defence360agent.contracts.configrrdefence360agent.myimunify.modelrrr defence360agent.rpc_toolsr defence360agent.utilsr RootEndpointsr CommonEndpointsr'r%rrr[s9!!!!!!!!888888888888 -,,,,,''''''EEEEE-EEE&' ' ' ' ' v5' ' ' ' ' rdefence360agent/simple_rpc/__pycache__/myimunify.cpython-311.pyc0000644000000000000000000001111500000000000021642 0ustar r_j ddlZddlmZmZddlmcmcmZ ddl m Z m Z ddl mZmZmZddlmZddlmZGddejZGd d ejZdS) N)ListOptional)MyImunifyConfigis_mi_freemium_license) MyImunify#set_protection_status_for_all_usersupdate_users_protection)lookup)ScopeceZdZejZejdddee de fdZ ejdddZ ejddd Z d S) MyImunifyEndpoints myimunifyupdateitems protectioncJKt|j||dkd{ViS)Nenabled)r _sink)selfrrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/myimunify.pyrzMyImunifyEndpoints.updatesK% JzY6          z enable-allc@Kt|jdd{VdS)NTrrrs r enable_allzMyImunifyEndpoints.enable_alls01$*dCCCCCCCCCCCrz disable-allc@Kt|jdd{VdS)NFrrs r disable_allzMyImunifyEndpoints.disable_all s01$*eDDDDDDDDDDDrN) __name__ __module__ __qualname__r IM360SCOPEr bindrstrrrrrrr r s KEV[h''$s)(' V[l++DD,+DV[m,,EE-,EEErr cteZdZejZejddddee de e fdZ dS)MyImunifyCommonEndpointsrstatusNruserc Ktj}tj}||g}tjr|d{V|g}tt|d}tjdztj ddd||| dz}tjtj|}tj|t'd|DdS)Nz/?cloudlinux_advantage provisioningmy_imunify_account_protection)mactionsuiteusernamedomain server_ipc0g|]}|d|ddS)r)r)r1rr%).0items r z3MyImunifyCommonEndpoints.status..Hs9"&\l9KLLr)myimunify_enabledpurchase_page_url is_freemiumr)rPURCHASE_PAGE_URLhp HostingPanelENABLEDget_domains_per_usergetnextiterurllibparse urlencode get_server_iprselectwherer)in_dictsr)rrr) purchase_url panel_manager user_domainsr2responses rr(zMyImunifyCommonEndpoints.status(sM&8 ))  FE& '<<>>>>>>>>#dB--d<00$77#5l,,!7&4%D(,&,)6)D)D)F)F    #%%++IN,>,>u,E,EFFLLNN!0!8!-133$    r)N) rrr r r!r"r r#rr$rr(r%rrr'r'%sc KEV[h''# # $s)# 8C=# # # ('# # # rr') urllib.parserCtypingrr+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelr< defence360agent.contracts.configrrdefence360agent.myimunify.modelrrr defence360agent.rpc_toolsr defence360agent.utilsr RootEndpointsr CommonEndpointsr'r%rrr[s9!!!!!!!!888888888888 -,,,,,''''''EEEEE-EEE&' ' ' ' ' v5' ' ' ' ' rdefence360agent/simple_rpc/__pycache__/permissions.cpython-311.opt-1.pyc0000644000000000000000000000173100000000000023131 0ustar r_j=>ddlmZddlmZmZGddeZdS)permissions_list)CommonEndpointsbindc:eZdZeddddZdS)PermissionEndpoints permissionslistNc4Kdt|d{ViS)Nitemsr)selfusers [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/permissions.pyrz$PermissionEndpoints.permissions_lists,/5555555566)N)__name__ __module__ __qualname__rrrrrrs? T-  777! 777rrN)%defence360agent.contracts.permissionsr defence360agent.rpc_tools.lookuprrrrrrrscBBBBBBBBBBBBBB77777/77777rdefence360agent/simple_rpc/__pycache__/permissions.cpython-311.pyc0000644000000000000000000000173100000000000022172 0ustar r_j=>ddlmZddlmZmZGddeZdS)permissions_list)CommonEndpointsbindc:eZdZeddddZdS)PermissionEndpoints permissionslistNc4Kdt|d{ViS)Nitemsr)selfusers [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/permissions.pyrz$PermissionEndpoints.permissions_lists,/5555555566)N)__name__ __module__ __qualname__rrrrrrs? T-  777! 777rrN)%defence360agent.contracts.permissionsr defence360agent.rpc_tools.lookuprrrrrrrscBBBBBBBBBBBBBB77777/77777rdefence360agent/simple_rpc/__pycache__/plesk_stats.cpython-311.opt-1.pyc0000644000000000000000000001611500000000000023114 0ustar r_j5ddlZddlZddlmZddlmZddlmZmZddl m Z ddl m Z ddl mZddlmZdd lmZdd lmZdd lmZejd d dZGddeZdS)N)suppress) LicenseCLN) RootEndpointsbind)run_in_executor_decorator) HostingPanel)list_docroots_domains_users)atomic_rewrite)Plesk) kernel_care)importerzimav.malwarelib.model MalwareHit)modulenamedefaultcfeZdZdZeddZedZedZ dS)PleskStatsEndpointsdz plesk-statscDKt}t|ts Jdtt t j}tt j |t j j }| td{Vd{V}d|dz|d||d{VdtjrdndiiS)Nzonly for pleskitemsi) last_modifiedlast_modified_strlicenser)r isinstancer introunddatetimenow timestampstr fromtimestamptimezoneutc_domains_statsr _get_stats_field_in_plugin_inforis_valid)selfpanelcurrent_timestampr domains_statss [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/plesk_stats.py plesk_statszPleskStatsEndpoints.plesk_statssZ%''99)999'h&7&;&;&=&=&G&G&I&I J JKK   + +!!%     #11-// / / / / / /         !2T!9%6 ==????????  !4!6!6=AAA   cKtjd{VsiStjd{V}ddd}t t 5t tjj5}tj |}dddn #1swxYwYdddn #1swxYwY|ddk}|d|dkr/tj tj j n4tj |dtj j }|sdn6tj tj j |z j}t!tjjtj|d|dd |d |d S) N)effective_kernelfirst_time_update_available updateCode1effectiveKernelr0)tzr1rF)backup autoUpdate)kernel_uptodateoutdated_since_days)r KernelCarecheck_installedget_plugin_inforFileNotFoundErroropen KC_PROPERTIESjsonloadrrr#r$r"daysr dumpsr )cls plugin_infopreviousfileupdate_availabler1r9s r,r&z3PleskStatsEndpoints._get_stats_field_in_plugin_info/s{ +--==???????? I'244DDFFFFFFFF $+/  ' ( ( + +k,:;; +t9T?? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'|4;,-:L1MMM   ! !X%6%: ! ; ; ;"00679J9N $$ AA!%%):)>%??-.    " 0 J(34E(F3N3X3X3Z3Z     +<8#6   s64C B4( C 4B8 8C ;B8 <C  CCc  tgddStttjt}tdtttjt D tt fd|}g}|D]9\}}}|D]0\}| |r| |n1:|d|j t|dS)Nr)infected_siteswsites_infectedc3&K|] }|dV dS)rN).0datas r, z5PleskStatsEndpoints._domains_stats..is:   G      r.c|dvS)NrM)rOinfected_userss r,z4PleskStatsEndpoints._domains_stats..tsT!W6r.)rlistselect orig_filewhere is_infectedtuplessetuserdistinctfilter startswithappendMAX_DOMAINS_COUNTlen) r(plesk_response file_namesinfected_plesk_responserJdocrootdomainr\filenamerSs @r,r%z"PleskStatsEndpoints._domains_statsZs  "$#$    j2 3 3 U:))++ , , VXX    !!*/22z--//00      #' 6666  # #  %<   !GVT)   &&w//"))&111E --Et/E-EF">22   r.N) __name__ __module__ __qualname__rarr- classmethodr&rr%rMr.r,rrsv T-   .( ( [( T( ( ( ( ( r.r)rr@ contextlibr!defence360agent.contracts.licenser defence360agent.rpc_tools.lookuprrdefence360agent.rpc_tools.utilsr+defence360agent.subsys.panels.hosting_panelr'defence360agent.subsys.panels.plesk.apir defence360agent.utilsr #defence360agent.subsys.panels.pleskr defence360agent.subsys.featuresr r getrrrMr.r,rws. 888888@@@@@@@@EEEEEEDDDDDDOOOOOO000000555555777777****** X\ "t o o o o o -o o o o o r.defence360agent/simple_rpc/__pycache__/plesk_stats.cpython-311.pyc0000644000000000000000000001611500000000000022155 0ustar r_j5ddlZddlZddlmZddlmZddlmZmZddl m Z ddl m Z ddl mZddlmZdd lmZdd lmZdd lmZejd d dZGddeZdS)N)suppress) LicenseCLN) RootEndpointsbind)run_in_executor_decorator) HostingPanel)list_docroots_domains_users)atomic_rewrite)Plesk) kernel_care)importerzimav.malwarelib.model MalwareHit)modulenamedefaultcfeZdZdZeddZedZedZ dS)PleskStatsEndpointsdz plesk-statscDKt}t|ts Jdtt t j}tt j |t j j }| td{Vd{V}d|dz|d||d{VdtjrdndiiS)Nzonly for pleskitemsi) last_modifiedlast_modified_strlicenser)r isinstancer introunddatetimenow timestampstr fromtimestamptimezoneutc_domains_statsr _get_stats_field_in_plugin_inforis_valid)selfpanelcurrent_timestampr domains_statss [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/plesk_stats.py plesk_statszPleskStatsEndpoints.plesk_statssZ%''99)999'h&7&;&;&=&=&G&G&I&I J JKK   + +!!%     #11-// / / / / / /         !2T!9%6 ==????????  !4!6!6=AAA   cKtjd{VsiStjd{V}ddd}t t 5t tjj5}tj |}dddn #1swxYwYdddn #1swxYwY|ddk}|d|dkr/tj tj j n4tj |dtj j }|sdn6tj tj j |z j}t!tjjtj|d|dd |d |d S) N)effective_kernelfirst_time_update_available updateCode1effectiveKernelr0)tzr1rF)backup autoUpdate)kernel_uptodateoutdated_since_days)r KernelCarecheck_installedget_plugin_inforFileNotFoundErroropen KC_PROPERTIESjsonloadrrr#r$r"daysr dumpsr )cls plugin_infopreviousfileupdate_availabler1r9s r,r&z3PleskStatsEndpoints._get_stats_field_in_plugin_info/s{ +--==???????? I'244DDFFFFFFFF $+/  ' ( ( + +k,:;; +t9T?? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +'|4;,-:L1MMM   ! !X%6%: ! ; ; ;"00679J9N $$ AA!%%):)>%??-.    " 0 J(34E(F3N3X3X3Z3Z     +<8#6   s64C B4( C 4B8 8C ;B8 <C  CCc  tgddStttjt}tdtttjt D tt fd|}g}|D]9\}}}|D]0\}| |r| |n1:|d|j t|dS)Nr)infected_siteswsites_infectedc3&K|] }|dV dS)rN).0datas r, z5PleskStatsEndpoints._domains_stats..is:   G      r.c|dvS)NrM)rOinfected_userss r,z4PleskStatsEndpoints._domains_stats..tsT!W6r.)rlistselect orig_filewhere is_infectedtuplessetuserdistinctfilter startswithappendMAX_DOMAINS_COUNTlen) r(plesk_response file_namesinfected_plesk_responserJdocrootdomainr\filenamerSs @r,r%z"PleskStatsEndpoints._domains_statsZs  "$#$    j2 3 3 U:))++ , , VXX    !!*/22z--//00      #' 6666  # #  %<   !GVT)   &&w//"))&111E --Et/E-EF">22   r.N) __name__ __module__ __qualname__rarr- classmethodr&rr%rMr.r,rrsv T-   .( ( [( T( ( ( ( ( r.r)rr@ contextlibr!defence360agent.contracts.licenser defence360agent.rpc_tools.lookuprrdefence360agent.rpc_tools.utilsr+defence360agent.subsys.panels.hosting_panelr'defence360agent.subsys.panels.plesk.apir defence360agent.utilsr #defence360agent.subsys.panels.pleskr defence360agent.subsys.featuresr r getrrrMr.r,rws. 888888@@@@@@@@EEEEEEDDDDDDOOOOOO000000555555777777****** X\ "t o o o o o -o o o o o r.defence360agent/simple_rpc/__pycache__/reputation_management.cpython-311.opt-1.pyc0000644000000000000000000000671200000000000025150 0ustar r_jddlZddlmZddlmZmZddlmZddlm Z ddl m Z ddl m Z ddlmZejeZGd d ejZdS) N)lookup)ValidationErrorvalidate_av_plus_license)PanelException)InfectedDomainList) hosting_panel) ReputationAPI)run_in_executorceZdZejdedZejdedZdS)ReputationManagementEndpointszinfected-domainscKttjd{V}t j|||\}}||dS)N)offsetlimit)items max_count)setr HostingPanel get_usersr get_by_user)selfrrexisting_usersrrs e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/reputation_management.py list_domainsz*ReputationManagementEndpoints.list_domainssv=#=#?#?#I#I#K#KKKKKKKLL-9 6   y"   z check-domainscKtj}|std |d{V}n.#t $r!}tt |d}~wwxYw|stdtj|d{Vtj d{Vtdfdd{VdS)Nz!No avaliable control panel found!zDomains not foundc.tjS)N)rrefresh_domains)domain_to_userreputation_datasrz=ReputationManagementEndpoints.check_domains..6s&6r) rr is_installedrget_user_domainsrstrr checkget_domain_to_ownerr )rhpdomainserrs @@r check_domainsz+ReputationManagementEndpoints.check_domainssY ' ) )   G!"EFF F *//11111111GG * * *!#a&&)) ) * 7!"566 6 - 3G < <<<<<<<,..BBDD D D D D D D                  sA B!A==BN)__name__ __module__ __qualname__rbindrrr)rrr r syV[#$$  %$ V[!!  "!   rr )loggingdefence360agent.rpc_toolsr"defence360agent.rpc_tools.validaterr"defence360agent.subsys.panels.baser%defence360agent.model.infected_domainrdefence360agent.subsys.panelsr%defence360agent.api.server.reputationr $defence360agent.model.simplificationr getLoggerr*logger RootEndpointsr r.rrr:s,,,,,,>=====DDDDDD777777??????@@@@@@  8 $ $) ) ) ) ) F$8) ) ) ) ) rdefence360agent/simple_rpc/__pycache__/reputation_management.cpython-311.pyc0000644000000000000000000000671200000000000024211 0ustar r_jddlZddlmZddlmZmZddlmZddlm Z ddl m Z ddl m Z ddlmZejeZGd d ejZdS) N)lookup)ValidationErrorvalidate_av_plus_license)PanelException)InfectedDomainList) hosting_panel) ReputationAPI)run_in_executorceZdZejdedZejdedZdS)ReputationManagementEndpointszinfected-domainscKttjd{V}t j|||\}}||dS)N)offsetlimit)items max_count)setr HostingPanel get_usersr get_by_user)selfrrexisting_usersrrs e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/reputation_management.py list_domainsz*ReputationManagementEndpoints.list_domainssv=#=#?#?#I#I#K#KKKKKKKLL-9 6   y"   z check-domainscKtj}|std |d{V}n.#t $r!}tt |d}~wwxYw|stdtj|d{Vtj d{Vtdfdd{VdS)Nz!No avaliable control panel found!zDomains not foundc.tjS)N)rrefresh_domains)domain_to_userreputation_datasrz=ReputationManagementEndpoints.check_domains..6s&6r) rr is_installedrget_user_domainsrstrr checkget_domain_to_ownerr )rhpdomainserrs @@r check_domainsz+ReputationManagementEndpoints.check_domainssY ' ) )   G!"EFF F *//11111111GG * * *!#a&&)) ) * 7!"566 6 - 3G < <<<<<<<,..BBDD D D D D D D                  sA B!A==BN)__name__ __module__ __qualname__rbindrrr)rrr r syV[#$$  %$ V[!!  "!   rr )loggingdefence360agent.rpc_toolsr"defence360agent.rpc_tools.validaterr"defence360agent.subsys.panels.baser%defence360agent.model.infected_domainrdefence360agent.subsys.panelsr%defence360agent.api.server.reputationr $defence360agent.model.simplificationr getLoggerr*logger RootEndpointsr r.rrr:s,,,,,,>=====DDDDDD777777??????@@@@@@  8 $ $) ) ) ) ) F$8) ) ) ) ) rdefence360agent/simple_rpc/__pycache__/schema.cpython-311.opt-1.pyc0000644000000000000000000002115200000000000022015 0ustar r_jddlmZmZddlmZmZddlmZddlm Z m Z m Z m Z m Z mZmZmZmZmZmZmZddlmZGddeZdZd S) )BaseErrorHandlerBasicErrorHandler)DefinitionSchemaUnvalidatedSchema)UserType) add_eula add_licenseadd_license_user add_versioncollect_warningscountsdefault_to_items max_countpreserve_remote_addrresolve_caller_panel_loginsend_command_invoke_messageset_caller_type_context)prepare_schemacLeZdZejZdZdZdS) ErrorHandlerc #*K|jr'|jD]}||Ed{VdSd|j|j|j|jdj|j|j |j|jdVdS)Nz#field: '{}', value: '{}', error: {}) constraintfieldvalue) child_errorscollect_errorsformatrrmessagesgetcodeinfor)selferrorerrs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/schema.pyrzErrorHandler.collect_errorss   ) 4 4..s3333333333 4 48>>  8 !!%*b118Z$/++        cng}|D]/}||D]}||0|S)N)rappend)r#errorsstring_representationr$r"s r&__call__zErrorHandler.__call__*sX " 3 3E++E22 3 3%,,T2222 3%$r'N)__name__ __module__ __qualname__rrcopyrr,r'r&rrsF )..00H"%%%%%r'rcj |ttjt|t}idt t jt jfftt jfftt jt jff||t jt jfftt jfftt jfftt jfftt jt jfftt jt jfft t jt jffg dt"t jt jffgdt$t jt jffgdt"t jt jffgdt&t jt jffgdt&t jt jffgdt$t jt jffgdt$t jt jffgd t$t jt jffgd t"t jt jffgd t"t jt jffgd t$t jt jffgd t"t jt jffgdt$t jt jffgdt$t jt jffgdt$t jt jffgdt$t jt jffgt$t jt jffgt$t jt jffgt"t jt jffgt"t jt jffgt&t jt jffgt&t jt jffgd}idtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgd tgd!tgd"tgd#tgid$tgd%tgd&tgd'tgd(tgd)tgd*tgd+tgd,tgd-tgd.tgd/tgd0tgd1tgd2tgd3tgd4tgtgtgtgtgtgtgtgtgtgtgtgtgtgtgd5}|||fS)6N) error_handler) whitelistiplist) blacklistr5r6)graylistr5r6)r4r5add)r7r5r9)r4countryr6)r7r:r6)r8r:r6)r7)r4)zwhitelisted-crawlersr6)z blocked-portr6)zblocked-port-ipr6)rules list-disabled)wordpress-pluginr;r<)r=z list-sites)) proactiveignorer6)feature-managementshow)ip-listsynced)rBlocalr6)rBrDr9)rBrDdelete)z enable-plugin)zdisable-plugin)zswitch-max-webserver)zinstall-vendors)zuninstall-vendors)z add-sudouser)zdelete-sudouser)doctor)captchazupdate-localizations)rGzcompile-localizations)update)kcarectlzdisable-auto-update)rIzenable-auto-update)rIz plugin-info)register) unregister)rstatus)zupdate-license)3rdpartyr6)z admin-emails)z list-docroots)featuresr6)rNstatus)rNinstall)rNremove)r@nativeenable)r@rRdisable)r@rRrO)importwblist)r;zupdate-app-specific-rules)supportsend)rM conflicts) smtp-blockingreset)rZsync))malwarez on-demandzcheck-detached)checkdb)zrestore-configs)patchmanusers)r_rJ)r_rP)r_migrate)r_ uninstall)r_rO)r_rPrealtime)r_rbrc)analyst-cleanuprequest)rdz get-requests)rdz is-allowed)rrexpandrrrrROOTNON_ROOTrrr r rr r rr rr)schema_validatorvalidate_middleware schema_paths _validator _middleware_middleware_excludes r&init_validatorro3sH "!  #N<$@$@ A A  # JW %x}h6G&H I((*;)= > (8=(:K*L M$#J// 12  8=* +  13 4  ' ( 8=(*;< =  x/@A B  x/@A B1 W6 $ hmX%67 8& 7W< $ (9: ;& =WB # hmX%67 8% CWH # !HM83D#E F% IWN # !HM83D#E F% OWT ) (9: ;+ UWZ ) (9: ;+ [W` ( (9: ;* aWf &8=(2C"DEFgWh &8=(2C"DEFiWj ) (9: ;+ kWp ! hmX%67 8# qWv $ (9: ;& wW| # (9: ;% }WB 7 (9: ;9 CWH + (9: ;- IWP(9: ;* (9: ;) "((-9J)K LM hmX%67 8' "HM83D#E F& "HM83D#E F) iWWWKr1XJ1hZ1 "H:1 xj 1  1 H: 1 xj1 hZ1 ,hZ1 -xj1 hZ1 ,hZ1 +XJ1 $hZ1 z1 (!1" xj#11$ hZ%1& xj'1( H:)1* XJ+1, xj-1.  /10 (112  314 3XJ516 4hZ718 3XJ91: xj;1< / =1> hZ?1@ "H:A1B #XJC1D "H:E11F6>Jj'j (z#+*"*"*$,:!) .6Z08z)1 .6Z,4:a111f {$7 77r'N)cerberus.errorsrrcerberus.schemarr defence360agent.contracts.configr$defence360agent.rpc_tools.middlewarerr r r r r rrrrrrdefence360agent.rpc_tools.utilsrrror1r'r&rus0????????????????555555                            ;:::::%%%%%#%%%:Z8Z8Z8Z8Z8r'defence360agent/simple_rpc/__pycache__/schema.cpython-311.pyc0000644000000000000000000002115200000000000021056 0ustar r_jddlmZmZddlmZmZddlmZddlm Z m Z m Z m Z m Z mZmZmZmZmZmZmZddlmZGddeZdZd S) )BaseErrorHandlerBasicErrorHandler)DefinitionSchemaUnvalidatedSchema)UserType) add_eula add_licenseadd_license_user add_versioncollect_warningscountsdefault_to_items max_countpreserve_remote_addrresolve_caller_panel_loginsend_command_invoke_messageset_caller_type_context)prepare_schemacLeZdZejZdZdZdS) ErrorHandlerc #*K|jr'|jD]}||Ed{VdSd|j|j|j|jdj|j|j |j|jdVdS)Nz#field: '{}', value: '{}', error: {}) constraintfieldvalue) child_errorscollect_errorsformatrrmessagesgetcodeinfor)selferrorerrs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/schema.pyrzErrorHandler.collect_errorss   ) 4 4..s3333333333 4 48>>  8 !!%*b118Z$/++        cng}|D]/}||D]}||0|S)N)rappend)r#errorsstring_representationr$r"s r&__call__zErrorHandler.__call__*sX " 3 3E++E22 3 3%,,T2222 3%$r'N)__name__ __module__ __qualname__rrcopyrr,r'r&rrsF )..00H"%%%%%r'rcj |ttjt|t}idt t jt jfftt jfftt jt jff||t jt jfftt jfftt jfftt jfftt jt jfftt jt jfft t jt jffg dt"t jt jffgdt$t jt jffgdt"t jt jffgdt&t jt jffgdt&t jt jffgdt$t jt jffgdt$t jt jffgd t$t jt jffgd t"t jt jffgd t"t jt jffgd t$t jt jffgd t"t jt jffgdt$t jt jffgdt$t jt jffgdt$t jt jffgdt$t jt jffgt$t jt jffgt$t jt jffgt"t jt jffgt"t jt jffgt&t jt jffgt&t jt jffgd}idtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgdtgd tgd!tgd"tgd#tgid$tgd%tgd&tgd'tgd(tgd)tgd*tgd+tgd,tgd-tgd.tgd/tgd0tgd1tgd2tgd3tgd4tgtgtgtgtgtgtgtgtgtgtgtgtgtgtgd5}|||fS)6N) error_handler) whitelistiplist) blacklistr5r6)graylistr5r6)r4r5add)r7r5r9)r4countryr6)r7r:r6)r8r:r6)r7)r4)zwhitelisted-crawlersr6)z blocked-portr6)zblocked-port-ipr6)rules list-disabled)wordpress-pluginr;r<)r=z list-sites)) proactiveignorer6)feature-managementshow)ip-listsynced)rBlocalr6)rBrDr9)rBrDdelete)z enable-plugin)zdisable-plugin)zswitch-max-webserver)zinstall-vendors)zuninstall-vendors)z add-sudouser)zdelete-sudouser)doctor)captchazupdate-localizations)rGzcompile-localizations)update)kcarectlzdisable-auto-update)rIzenable-auto-update)rIz plugin-info)register) unregister)rstatus)zupdate-license)3rdpartyr6)z admin-emails)z list-docroots)featuresr6)rNstatus)rNinstall)rNremove)r@nativeenable)r@rRdisable)r@rRrO)importwblist)r;zupdate-app-specific-rules)supportsend)rM conflicts) smtp-blockingreset)rZsync))malwarez on-demandzcheck-detached)checkdb)zrestore-configs)patchmanusers)r_rJ)r_rP)r_migrate)r_ uninstall)r_rO)r_rPrealtime)r_rbrc)analyst-cleanuprequest)rdz get-requests)rdz is-allowed)rrexpandrrrrROOTNON_ROOTrrr r rr r rr rr)schema_validatorvalidate_middleware schema_paths _validator _middleware_middleware_excludes r&init_validatorro3sH "!  #N<$@$@ A A  # JW %x}h6G&H I((*;)= > (8=(:K*L M$#J// 12  8=* +  13 4  ' ( 8=(*;< =  x/@A B  x/@A B1 W6 $ hmX%67 8& 7W< $ (9: ;& =WB # hmX%67 8% CWH # !HM83D#E F% IWN # !HM83D#E F% OWT ) (9: ;+ UWZ ) (9: ;+ [W` ( (9: ;* aWf &8=(2C"DEFgWh &8=(2C"DEFiWj ) (9: ;+ kWp ! hmX%67 8# qWv $ (9: ;& wW| # (9: ;% }WB 7 (9: ;9 CWH + (9: ;- IWP(9: ;* (9: ;) "((-9J)K LM hmX%67 8' "HM83D#E F& "HM83D#E F) iWWWKr1XJ1hZ1 "H:1 xj 1  1 H: 1 xj1 hZ1 ,hZ1 -xj1 hZ1 ,hZ1 +XJ1 $hZ1 z1 (!1" xj#11$ hZ%1& xj'1( H:)1* XJ+1, xj-1.  /10 (112  314 3XJ516 4hZ718 3XJ91: xj;1< / =1> hZ?1@ "H:A1B #XJC1D "H:E11F6>Jj'j (z#+*"*"*$,:!) .6Z08z)1 .6Z,4:a111f {$7 77r'N)cerberus.errorsrrcerberus.schemarr defence360agent.contracts.configr$defence360agent.rpc_tools.middlewarerr r r r r rrrrrrdefence360agent.rpc_tools.utilsrrror1r'r&rus0????????????????555555                            ;:::::%%%%%#%%%:Z8Z8Z8Z8Z8r'defence360agent/simple_rpc/__pycache__/wordpress_security_plugin.cpython-311.opt-1.pyc0000644000000000000000000002263600000000000026122 0ustar r_j "ddlZddlZddlZddlmZddlmZmZmZddl m Z m Z ddl m Z ddlmZddlmZddlmZdd lmZejeZ dd edzd edzd eedzedzffd ZGddeZGddeZdS)N)ValidationError)CommonEndpoints RootEndpointsbind)Scope is_root_user) MessageType)get_wordpress_incidents)$enrich_incidents_with_disabled_state)get_installed_sites_paginated)get_domain_pathsuser site_searchreturncxtj}trtd|d}|s t j|j}td||n<#t$r/t d|td|dwxYw||fS||fS)a Determine the user_id and site_path for filtering WordPress incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents (user/site_search ignored) 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by site_search: Site path to filter by Returns: Tuple of (user_id, site_path) to filter by, or (None, None) for all Raises: KeyError: If the specified user doesn't exist ValueError: If proxy service call is missing required parameters z-Root user querying incidents, user filter: %sNz(Filtering incidents for user %s (uid=%d)zUser not found: %szUser 'z ' not found) osgetuidrloggerdebugpwdgetpwnampw_uidKeyErrorwarning)rr current_uiduser_ids i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wordpress_security_plugin.pyget_user_id_and_site_for_queryrs,)++K~~$ DdKKK   ;,t,,3 >g ; ; ;3T:::9999::: ; ##  ##s 5A889B1ceZdZejZedddZedddZedddZ eddd Z d S) WordpressEndpointswordpress-pluginzinstall-on-new-sitescpK|jtjdd{VdS)Ninstall_on_new_sitesaction_sinkprocess_messager WordpressPluginActionselfs rwordpress_plugin_installz+WordpressEndpoints.wordpress_plugin_installGsZj((  -5K L L L           ztidy-upcpK|jtjdd{VdS)Ntidy_upr$r&r*s rwordpress_plugin_tidy_upz+WordpressEndpoints.wordpress_plugin_tidy_upMsYj((  -Y ? ? ?           r-updatecpK|jtjdd{VdS)Nupdate_existingr$r&r*s rwordpress_plugin_updatez*WordpressEndpoints.wordpress_plugin_updateSsZj((  -5F G G G           r-zinstall-and-updatecpK|jtjdd{VdS)Ninstall_and_updater$r&r*s r#wordpress_plugin_install_and_updatez6WordpressEndpoints.wordpress_plugin_install_and_updateYsZj((  -5I J J J           r-N) __name__ __module__ __qualname__rAV_IM360SCOPErr,r0r4r7r-rr r Ds NE T 455  65  T i((  )(  T h''  ('  T 233  43   r-r ceZdZejZedd ddedzdedzd ed ed edzd edzd edzdedzdedzdedzde dzde de e fdZ eddddZ dS)WordpressCommonEndpointsr!zlist-incidentsN2rFrrlimitoffset by_abuser_ipby_country_code by_domainsearchsincetoorder_byinclude_hiddenrc `K t||\} }n/#t$r"}tt||d}~wwxYwt ||| |||||| | | |  }|D]:}|d|d<|d}|d|ind|d<;t ||S)ac List WordPress security incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by (root or proxy service) site_search: Site path to filter by (proxy service only) limit: Maximum number of incidents to return offset: Number of incidents to skip by_abuser_ip: Filter by attacker IP address by_country_code: Filter by country code by_domain: Filter by domain search: Search across multiple fields since: Filter by timestamp >= this value (unix timestamp) to: Filter by timestamp <= this value (unix timestamp) order_by: List of fields to order by (e.g., ['timestamp-', 'severity-']) Returns: List of incident dictionaries Raises: ValidationError: If the specified user doesn't exist N) rArBrrCrDrErFrrGrHrIrJretriestimescountrycode)rrrstrr popr )r+rrrArBrCrDrErFrGrHrIrJr site_pathe incidentsincidentrNs rwordpress_plugin_list_incidentsz8WordpressCommonEndpoints.wordpress_plugin_list_incidentscsV 1!?k"" GYY 1 1 1!#a&&))q 0 1,%+!)    "  H ( Y 7 7HW ll9--G%,%8!!d Y   -Y777s A?Az list-sitescfKd}|r/ tj|j}n#t$rdgfcYSwxYwt |||\}}t d{V}g}|D]K} || jg} | r| dn| j} | | | jdL||fS)z List WordPress sites with Imunify plugin installed. For root users: returns all sites. For non-root users: returns only sites belonging to that user. Nr)uidrArB)domaindocroot) rrrrr r getrZrYappend) r+rArBrrX max_countsitesdocroot_domainsitemssitedomainsprimary_domains r list_sitesz#WordpressCommonEndpoints.list_sitess   l4((/   "u  95    5 !1 2 2222222  D%))$,;;G+2CWQZZ N LL,#|    %s "33) NNr@rNNNNNNNF)r@rN)r8r9r:rr;r<rrPintlistbooldictrVrdr=r-rr?r?`s\ NE T .// "&#'&* $!  $$JJDjJ4ZJ J  J Dj JtJ:Jd JTzJ $JJ+JJ dJJJ0/JX T l++# # # ,+# # # r-r?)NN)loggingrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrrdefence360agent.utilsrr"defence360agent.contracts.messagesr (defence360agent.model.wordpress_incidentr &defence360agent.model.wp_disabled_ruler )defence360agent.wordpress.site_repositoryr defence360agent.wordpress.utilsr getLoggerr8rrPtuplererr r?r=r-rrts 555555 65555555::::::LLLLLL=<<<<<  8 $ $8<($($ *($*-*($ 3:sTz !"($($($($V        8t t t t t t t t t t r-defence360agent/simple_rpc/__pycache__/wordpress_security_plugin.cpython-311.pyc0000644000000000000000000002263600000000000025163 0ustar r_j "ddlZddlZddlZddlmZddlmZmZmZddl m Z m Z ddl m Z ddlmZddlmZddlmZdd lmZejeZ dd edzd edzd eedzedzffd ZGddeZGddeZdS)N)ValidationError)CommonEndpoints RootEndpointsbind)Scope is_root_user) MessageType)get_wordpress_incidents)$enrich_incidents_with_disabled_state)get_installed_sites_paginated)get_domain_pathsuser site_searchreturncxtj}trtd|d}|s t j|j}td||n<#t$r/t d|td|dwxYw||fS||fS)a Determine the user_id and site_path for filtering WordPress incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents (user/site_search ignored) 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by site_search: Site path to filter by Returns: Tuple of (user_id, site_path) to filter by, or (None, None) for all Raises: KeyError: If the specified user doesn't exist ValueError: If proxy service call is missing required parameters z-Root user querying incidents, user filter: %sNz(Filtering incidents for user %s (uid=%d)zUser not found: %szUser 'z ' not found) osgetuidrloggerdebugpwdgetpwnampw_uidKeyErrorwarning)rr current_uiduser_ids i/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wordpress_security_plugin.pyget_user_id_and_site_for_queryrs,)++K~~$ DdKKK   ;,t,,3 >g ; ; ;3T:::9999::: ; ##  ##s 5A889B1ceZdZejZedddZedddZedddZ eddd Z d S) WordpressEndpointswordpress-pluginzinstall-on-new-sitescpK|jtjdd{VdS)Ninstall_on_new_sitesaction_sinkprocess_messager WordpressPluginActionselfs rwordpress_plugin_installz+WordpressEndpoints.wordpress_plugin_installGsZj((  -5K L L L           ztidy-upcpK|jtjdd{VdS)Ntidy_upr$r&r*s rwordpress_plugin_tidy_upz+WordpressEndpoints.wordpress_plugin_tidy_upMsYj((  -Y ? ? ?           r-updatecpK|jtjdd{VdS)Nupdate_existingr$r&r*s rwordpress_plugin_updatez*WordpressEndpoints.wordpress_plugin_updateSsZj((  -5F G G G           r-zinstall-and-updatecpK|jtjdd{VdS)Ninstall_and_updater$r&r*s r#wordpress_plugin_install_and_updatez6WordpressEndpoints.wordpress_plugin_install_and_updateYsZj((  -5I J J J           r-N) __name__ __module__ __qualname__rAV_IM360SCOPErr,r0r4r7r-rr r Ds NE T 455  65  T i((  )(  T h''  ('  T 233  43   r-r ceZdZejZedd ddedzdedzd ed ed edzd edzd edzdedzdedzdedzde dzde de e fdZ eddddZ dS)WordpressCommonEndpointsr!zlist-incidentsN2rFrrlimitoffset by_abuser_ipby_country_code by_domainsearchsincetoorder_byinclude_hiddenrc `K t||\} }n/#t$r"}tt||d}~wwxYwt ||| |||||| | | |  }|D]:}|d|d<|d}|d|ind|d<;t ||S)ac List WordPress security incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by (root or proxy service) site_search: Site path to filter by (proxy service only) limit: Maximum number of incidents to return offset: Number of incidents to skip by_abuser_ip: Filter by attacker IP address by_country_code: Filter by country code by_domain: Filter by domain search: Search across multiple fields since: Filter by timestamp >= this value (unix timestamp) to: Filter by timestamp <= this value (unix timestamp) order_by: List of fields to order by (e.g., ['timestamp-', 'severity-']) Returns: List of incident dictionaries Raises: ValidationError: If the specified user doesn't exist N) rArBrrCrDrErFrrGrHrIrJretriestimescountrycode)rrrstrr popr )r+rrrArBrCrDrErFrGrHrIrJr site_pathe incidentsincidentrNs rwordpress_plugin_list_incidentsz8WordpressCommonEndpoints.wordpress_plugin_list_incidentscsV 1!?k"" GYY 1 1 1!#a&&))q 0 1,%+!)    "  H ( Y 7 7HW ll9--G%,%8!!d Y   -Y777s A?Az list-sitescfKd}|r/ tj|j}n#t$rdgfcYSwxYwt |||\}}t d{V}g}|D]K} || jg} | r| dn| j} | | | jdL||fS)z List WordPress sites with Imunify plugin installed. For root users: returns all sites. For non-root users: returns only sites belonging to that user. Nr)uidrArB)domaindocroot) rrrrr r getrZrYappend) r+rArBrrX max_countsitesdocroot_domainsitemssitedomainsprimary_domains r list_sitesz#WordpressCommonEndpoints.list_sitess   l4((/   "u  95    5 !1 2 2222222  D%))$,;;G+2CWQZZ N LL,#|    %s "33) NNr@rNNNNNNNF)r@rN)r8r9r:rr;r<rrPintlistbooldictrVrdr=r-rr?r?`s\ NE T .// "&#'&* $!  $$JJDjJ4ZJ J  J Dj JtJ:Jd JTzJ $JJ+JJ dJJJ0/JX T l++# # # ,+# # # r-r?)NN)loggingrrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrrdefence360agent.utilsrr"defence360agent.contracts.messagesr (defence360agent.model.wordpress_incidentr &defence360agent.model.wp_disabled_ruler )defence360agent.wordpress.site_repositoryr defence360agent.wordpress.utilsr getLoggerr8rrPtuplererr r?r=r-rrts 555555 65555555::::::LLLLLL=<<<<<  8 $ $8<($($ *($*-*($ 3:sTz !"($($($($V        8t t t t t t t t t t r-defence360agent/simple_rpc/__pycache__/wp_disabled_rules.cpython-311.opt-1.pyc0000644000000000000000000003232000000000000024243 0ustar r_j(dZddlZddlZddlZddlZddlmZddlmZm Z ddl m Z ddl m Z mZddlmZddlmZdd lmZmZdd lmZdd lmZmZdd lmZdd lmZmZddl m!Z!ddl"m#Z#ej$e%Z&de'de(e'fdZ)de'de(e'dzde(e'fdZ*de(e+de+dzde(e+fdZ, dde(e'de dzddfdZ-GddeZ.dS)z6RPC endpoints for WordPress disabled protection rules.N) MessageType)WP_WAF_RULES_EDITcheck_permission) MessageSink)IndexWP_RULES)WPDisabledRule)ValidationError)CommonEndpointsbind) hosting_panel)Scopelog_future_errors)ChangelogProcessor)redeploy_rules_phpupdate_disabled_rules_on_sites)get_installed_sites_by_domains)get_wp_rules_datauserreturncK tj}|d{V}||gS#t$r(}t d||gcYd}~Sd}~wwxYw)z Get domains for a user from the hosting panel. Returns: List of domains the user owns, or empty list on error. Nz%Failed to get domains for user %s: %s)r HostingPanelget_domains_per_userget Exceptionloggerwarning)rhpdomains_per_useres a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_disabled_rules.py_get_user_domainsr"#s  ' ) )!#!8!8!:!:::::::##D"--- >aHHH sAA A9A4.A94A9domainscKt|d{V|sstdSfd|D}|std|S)a Validate and filter domains for a non-root user. If no domains specified, returns all user's domains. If domains specified, filters to only those the user owns. Args: user: Username to validate domains for domains: Requested domains, or None for all user's domains Returns: List of validated domains the user can access Raises: ValidationError: If user has no domains or no access to requested domains NzNo domains found for usercg|]}|v| Sr&.0d user_domainss r! z*_validate_user_domains..Ls#BBB\0A0A!0A0A0Az5You don't have access to any of the specified domains)r"r )rr#authorized_domainsr*s @r!_validate_user_domainsr.3s&+400000000L  ?!"=>> >BBBBWBBB   C    r,disabled_rules wp_rules_datacg}|D]f}|d}|r||ini}|i||d|ddg|S)a9 Enrich disabled rules with metadata from wp-rules.yaml. Args: disabled_rules: List of disabled rule dicts from WPDisabledRule.fetch() wp_rules_data: Parsed wp-rules.yaml data, or None if unavailable Returns: List of enriched rule dicts with component and versions added rule_idtargetversions) componentr4)rappend)r/r0enrichedruler2metadatas r!_enrich_with_metadatar:TsH   y/5BJ=$$Wb111  %\\(33$LL44        Or,sinkcK t|}|sdSt||d{VdS#t$r(}td|dYd}~dSd}~wwxYw)aProcess pending changelog files for the given domains before an API change. This "Just-in-Time" sync ensures the database reflects any WordPress-side changes before the agent applies its own disable/enable operation. File regeneration (disabled-rules.php) is intentionally skipped here because the calling API endpoint will regenerate files after its own DB mutation. N)r;zJIT changelog sync failed: %sT)exc_info)rrprocess_changelogs_for_sitesrrr)r#r;sitesr s r!_jit_sync_changelogsr@qsJ.w77  F ""?? @            JJJ6DIIIIIIIIIJsA)A A4 A//A4ceZdZdZejZeddd dded ed e e dzd e dzd e ee e ff d Z de de d e e dzd e dzd e f dZeddd dde d e e dzd e dzd e fdZeddd dde d e e dzd e dzd e fdZdS)WPDisabledRulesEndpointsz:Endpoints for listing disabled WordPress protection rules.zwordpress-pluginrulesz list-disabled2rNlimitoffsetr#rrc K|r.t|d{V |s }n fd|D}|sdgfStj||||du\}} ttd}t |}n4#t $r'} td| d}Yd} ~ nd} ~ wwxYwt||} || fS)a List disabled WordPress protection rules with metadata. When user is provided, returns rules for that user's domains. Otherwise, returns all disabled rules. Args: limit: Maximum number of rules to return offset: Number of rules to skip domains: Filter by specific domains (optional) user: Username (populated by middleware) Returns: Tuple of (total_count, list of enriched rule dicts) Ncg|]}|v| Sr&r&r's r!r+z@WPDisabledRulesEndpoints.list_disabled_rules..s#CCCl1B1B11B1B1Br,r)rErFr*include_globalF)integrity_checkz Failed to load wp-rules data: %s) r"r fetchrrrrrrr:) selfrErFr#r total_countr/wp_rules_indexr0r enriched_rulesr*s @r!list_disabled_rulesz,WPDisabledRulesEndpoints.list_disabled_ruless0  !!24!8!8888888L !&CCCCgCCC!b5L'5&: 4< ' ' ' # ^ !"8UCCCN-n==MM ! ! ! NN=q A A A MMMMMM ! /~}MMN**s%A88 B)B$$B)actionr8c Ktt|d{V|d}n< tj|j}n!#t $rt d|dwxYw|rt||d{V}|rt||j d{V|dkr/tj ||tj |tj}n"tj||tj} |j |d||pgt%j|tj d{Vn4#t&$r'}t(d |||Yd}~nd}~wwxYw|r#t-jt1| }n t-jt3}|t6iS) z8Shared implementation for disable/enable rule endpoints.NrzUser 'z ' not founddisable)r2r#sourceuser_id)r2r# wordpress) plugin_idr8r# timestamprUrTz#Failed to report rule %s for %s: %s)r#)rrpwdgetpwnampw_uidKeyErrorr r.r@_sinkr store SOURCE_AGENTrWPRuleDisabledremove WPRuleEnabledprocess_messagetimerrerrorasyncio create_taskrradd_done_callbackr) rLrQr8r#rrU message_clsr tasks r! _toggle_rulez%WPDisabledRulesEndpoints._toggle_ruleso0$777777777 <GG B,t,,3 B B B%&@t&@&@&@AAA B  B24AAAAAAAAG  <&w ;; ; ; ; ; ; ; ; Y    %2      &4KK  !$ @ @ @ @%3K *,, )#Mr"ikk#)6               LL5vtQ           =&.w???DD &'9';';<A,A D99 E*E%%E*rScBK|d|||d{VS)av Disable a WordPress protection rule globally or for specific domains. Root users can disable globally (no domains) or for specific domains. Non-root users can disable for all their domains (by specifying no domains) or for specific domains. Non-root users can only disable for domains they own. Args: rule: The rule ID to disable (e.g., "CVE-2025-001") domains: List of domains to disable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success. rSNrkrLr8r#rs r! disable_rulez%WPDisabledRulesEndpoints.disable_rules4.&&y$FFFFFFFFFr,enablecBK|d|||d{VS)a Re-enable a WordPress protection rule globally or for specific domains. Root users can enable globally (no domains) or for specific domains. Non-root users can enable for all their domains (no domains) or specific ones. Non-root users can only enable for domains they own. Note: Enabling at one scope doesn't affect the other scope. E.g., enabling globally leaves domain-specific disables intact. Args: rule: The rule ID to enable (e.g., "CVE-2025-001") domains: List of domains to enable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success rpNrmrns r! enable_rulez$WPDisabledRulesEndpoints.enable_rules44&&xwEEEEEEEEEr,)rDrNN)NN)__name__ __module__ __qualname____doc__rAV_IM360SCOPEr intliststrtupledictrPrkrorrr&r,r!rBrBsDD NE T g77$( 6+6+6+6+cT! 6+ Dj 6+ sDJ  6+6+6+876+p@@@cT! @ Dj @  @@@@D T gy11%) GGGcT!GDj G  GGG21G0 T gx00%) FFFcT!FDj F  FFF10FFFr,rB)N)/rvrfloggingrYrd"defence360agent.contracts.messagesr%defence360agent.contracts.permissionsrr!defence360agent.contracts.pluginsrdefence360agent.filesrr&defence360agent.model.wp_disabled_ruler defence360agent.rpc_toolsr defence360agent.rpc_tools.lookupr r defence360agent.subsys.panelsr defence360agent.utilsrr-defence360agent.wordpress.changelog_processorr defence360agent.wordpress.pluginrr)defence360agent.wordpress.site_repositoryr"defence360agent.wordpress.wp_rulesr getLoggerrsrr{rzr"r.r}r:r@rBr&r,r!rs<< :::::::9999911111111AAAAAA555555BBBBBBBB777777::::::::A@@@@@  8 $ $ # $s)      S D( #YBJ/3d{ $Z<48JJ #YJ)D0J JJJJ*sFsFsFsFsFsFsFsFsFsFr,defence360agent/simple_rpc/__pycache__/wp_disabled_rules.cpython-311.pyc0000644000000000000000000003232000000000000023304 0ustar r_j(dZddlZddlZddlZddlZddlmZddlmZm Z ddl m Z ddl m Z mZddlmZddlmZdd lmZmZdd lmZdd lmZmZdd lmZdd lmZmZddl m!Z!ddl"m#Z#ej$e%Z&de'de(e'fdZ)de'de(e'dzde(e'fdZ*de(e+de+dzde(e+fdZ, dde(e'de dzddfdZ-GddeZ.dS)z6RPC endpoints for WordPress disabled protection rules.N) MessageType)WP_WAF_RULES_EDITcheck_permission) MessageSink)IndexWP_RULES)WPDisabledRule)ValidationError)CommonEndpointsbind) hosting_panel)Scopelog_future_errors)ChangelogProcessor)redeploy_rules_phpupdate_disabled_rules_on_sites)get_installed_sites_by_domains)get_wp_rules_datauserreturncK tj}|d{V}||gS#t$r(}t d||gcYd}~Sd}~wwxYw)z Get domains for a user from the hosting panel. Returns: List of domains the user owns, or empty list on error. Nz%Failed to get domains for user %s: %s)r HostingPanelget_domains_per_userget Exceptionloggerwarning)rhpdomains_per_useres a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_disabled_rules.py_get_user_domainsr"#s  ' ) )!#!8!8!:!:::::::##D"--- >aHHH sAA A9A4.A94A9domainscKt|d{V|sstdSfd|D}|std|S)a Validate and filter domains for a non-root user. If no domains specified, returns all user's domains. If domains specified, filters to only those the user owns. Args: user: Username to validate domains for domains: Requested domains, or None for all user's domains Returns: List of validated domains the user can access Raises: ValidationError: If user has no domains or no access to requested domains NzNo domains found for usercg|]}|v| Sr&.0d user_domainss r! z*_validate_user_domains..Ls#BBB\0A0A!0A0A0Az5You don't have access to any of the specified domains)r"r )rr#authorized_domainsr*s @r!_validate_user_domainsr.3s&+400000000L  ?!"=>> >BBBBWBBB   C    r,disabled_rules wp_rules_datacg}|D]f}|d}|r||ini}|i||d|ddg|S)a9 Enrich disabled rules with metadata from wp-rules.yaml. Args: disabled_rules: List of disabled rule dicts from WPDisabledRule.fetch() wp_rules_data: Parsed wp-rules.yaml data, or None if unavailable Returns: List of enriched rule dicts with component and versions added rule_idtargetversions) componentr4)rappend)r/r0enrichedruler2metadatas r!_enrich_with_metadatar:TsH   y/5BJ=$$Wb111  %\\(33$LL44        Or,sinkcK t|}|sdSt||d{VdS#t$r(}td|dYd}~dSd}~wwxYw)aProcess pending changelog files for the given domains before an API change. This "Just-in-Time" sync ensures the database reflects any WordPress-side changes before the agent applies its own disable/enable operation. File regeneration (disabled-rules.php) is intentionally skipped here because the calling API endpoint will regenerate files after its own DB mutation. N)r;zJIT changelog sync failed: %sT)exc_info)rrprocess_changelogs_for_sitesrrr)r#r;sitesr s r!_jit_sync_changelogsr@qsJ.w77  F ""?? @            JJJ6DIIIIIIIIIJsA)A A4 A//A4ceZdZdZejZeddd dded ed e e dzd e dzd e ee e ff d Z de de d e e dzd e dzd e f dZeddd dde d e e dzd e dzd e fdZeddd dde d e e dzd e dzd e fdZdS)WPDisabledRulesEndpointsz:Endpoints for listing disabled WordPress protection rules.zwordpress-pluginrulesz list-disabled2rNlimitoffsetr#rrc K|r.t|d{V |s }n fd|D}|sdgfStj||||du\}} ttd}t |}n4#t $r'} td| d}Yd} ~ nd} ~ wwxYwt||} || fS)a List disabled WordPress protection rules with metadata. When user is provided, returns rules for that user's domains. Otherwise, returns all disabled rules. Args: limit: Maximum number of rules to return offset: Number of rules to skip domains: Filter by specific domains (optional) user: Username (populated by middleware) Returns: Tuple of (total_count, list of enriched rule dicts) Ncg|]}|v| Sr&r&r's r!r+z@WPDisabledRulesEndpoints.list_disabled_rules..s#CCCl1B1B11B1B1Br,r)rErFr*include_globalF)integrity_checkz Failed to load wp-rules data: %s) r"r fetchrrrrrrr:) selfrErFr#r total_countr/wp_rules_indexr0r enriched_rulesr*s @r!list_disabled_rulesz,WPDisabledRulesEndpoints.list_disabled_ruless0  !!24!8!8888888L !&CCCCgCCC!b5L'5&: 4< ' ' ' # ^ !"8UCCCN-n==MM ! ! ! NN=q A A A MMMMMM ! /~}MMN**s%A88 B)B$$B)actionr8c Ktt|d{V|d}n< tj|j}n!#t $rt d|dwxYw|rt||d{V}|rt||j d{V|dkr/tj ||tj |tj}n"tj||tj} |j |d||pgt%j|tj d{Vn4#t&$r'}t(d |||Yd}~nd}~wwxYw|r#t-jt1| }n t-jt3}|t6iS) z8Shared implementation for disable/enable rule endpoints.NrzUser 'z ' not founddisable)r2r#sourceuser_id)r2r# wordpress) plugin_idr8r# timestamprUrTz#Failed to report rule %s for %s: %s)r#)rrpwdgetpwnampw_uidKeyErrorr r.r@_sinkr store SOURCE_AGENTrWPRuleDisabledremove WPRuleEnabledprocess_messagetimerrerrorasyncio create_taskrradd_done_callbackr) rLrQr8r#rrU message_clsr tasks r! _toggle_rulez%WPDisabledRulesEndpoints._toggle_ruleso0$777777777 <GG B,t,,3 B B B%&@t&@&@&@AAA B  B24AAAAAAAAG  <&w ;; ; ; ; ; ; ; ; Y    %2      &4KK  !$ @ @ @ @%3K *,, )#Mr"ikk#)6               LL5vtQ           =&.w???DD &'9';';<A,A D99 E*E%%E*rScBK|d|||d{VS)av Disable a WordPress protection rule globally or for specific domains. Root users can disable globally (no domains) or for specific domains. Non-root users can disable for all their domains (by specifying no domains) or for specific domains. Non-root users can only disable for domains they own. Args: rule: The rule ID to disable (e.g., "CVE-2025-001") domains: List of domains to disable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success. rSNrkrLr8r#rs r! disable_rulez%WPDisabledRulesEndpoints.disable_rules4.&&y$FFFFFFFFFr,enablecBK|d|||d{VS)a Re-enable a WordPress protection rule globally or for specific domains. Root users can enable globally (no domains) or for specific domains. Non-root users can enable for all their domains (no domains) or specific ones. Non-root users can only enable for domains they own. Note: Enabling at one scope doesn't affect the other scope. E.g., enabling globally leaves domain-specific disables intact. Args: rule: The rule ID to enable (e.g., "CVE-2025-001") domains: List of domains to enable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success rpNrmrns r! enable_rulez$WPDisabledRulesEndpoints.enable_rules44&&xwEEEEEEEEEr,)rDrNN)NN)__name__ __module__ __qualname____doc__rAV_IM360SCOPEr intliststrtupledictrPrkrorrr&r,r!rBrBsDD NE T g77$( 6+6+6+6+cT! 6+ Dj 6+ sDJ  6+6+6+876+p@@@cT! @ Dj @  @@@@D T gy11%) GGGcT!GDj G  GGG21G0 T gx00%) FFFcT!FDj F  FFF10FFFr,rB)N)/rvrfloggingrYrd"defence360agent.contracts.messagesr%defence360agent.contracts.permissionsrr!defence360agent.contracts.pluginsrdefence360agent.filesrr&defence360agent.model.wp_disabled_ruler defence360agent.rpc_toolsr defence360agent.rpc_tools.lookupr r defence360agent.subsys.panelsr defence360agent.utilsrr-defence360agent.wordpress.changelog_processorr defence360agent.wordpress.pluginrr)defence360agent.wordpress.site_repositoryr"defence360agent.wordpress.wp_rulesr getLoggerrsrr{rzr"r.r}r:r@rBr&r,r!rs<< :::::::9999911111111AAAAAA555555BBBBBBBB777777::::::::A@@@@@  8 $ $ # $s)      S D( #YBJ/3d{ $Z<48JJ #YJ)D0J JJJJ*sFsFsFsFsFsFsFsFsFsFr,defence360agent/simple_rpc/__pycache__/wp_waf_bulk.cpython-311.opt-1.pyc0000644000000000000000000002577300000000000023072 0ustar r_jz dZddlZddlZddlZddlmZddlmZddlm Z m Z ddl m Z ddl mZddlmZdd lmZmZdd lmZejeZd Zd Zd ZdZdeedeeee dze!effdZ"deee dze!efde#e e fde#fdZ$deee dze!efdedzdedzde!fdZ%Gdde Z&dS)z Bulk WAF set + status endpoints.N) Wordpress)ValidationError) RootEndpointsbind) hosting_panel)Scope) update_config)waf_global_snapshot#waf_status_and_source_for_user_sync)count_installed_sites_by_uid enableddisablediusersreturncg}|D]Z} tj|j}n#t$rd}YnwxYwt |\}}|||||f[|S)N)pwdgetpwnampw_uidKeyErrorr append)rrowsnameuidrsources [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_waf_bulk.py_resolve_accounts_syncr"s D22 ,t$$+CC   CCC =dCC T301111 Ks " 11row site_countscb|\}}}}||rtnt|||ddS)Nr)r waf_statusrwp_sites)_STATUS_ENABLED_STATUS_DISABLEDget)rrrrrrs r _status_itemr&0sE"%D#w)0Foo6FOOC++   statusrc\|\}}}}|rtnt}|||krdS|||krdSdS)NFT)r#r$)rr(r_rsrcr!s r_matchesr,<sN Aq'3$+A1AJ jF22u cVmmu 4r'ceZdZejZeddd ddedede edzd e fd Z eddd dd edzdedzd edzde dzde d e f dZ dS)WordpressWafBulkEndpointszwordpress-pluginwafsetFNr( all_usersrrcXK|r|td|s|td||stdtjstdtd||| t t jd{V}n%#t$r}td||d}~wwxYwg}g}g}|rt|} nQg} t |D]4} | |vr| | | | dd5|d kd td tttdzfffd t!d t#| t$D]l} fd| | | t$zD} t'j| d{V} | D]5\} }|| | | | |d6mgd|Dd|Dd|D}||||dS)Nz/Specify either --all-users or --users, not bothz%Specify either --all-users or --usersz--users must not be emptyzNWordPress Security Plugin is disabled. Enable it before changing WAF settings.z>AUDIT wordpress-plugin.waf.set status=%r all_users=%r users=%r#Could not enumerate hosting users: zNot a hosting user)userreasonrurcK tjddii|d{V|dfS#t$r}|t|fcYd}~Sd}~wwxYw)N WORDPRESS waf_enabled)r4)r _sink Exceptionstr)r6eself waf_values r_apply_to_userz9WordpressWafBulkEndpoints.waf_set.._apply_to_users !#J =)"<= $w ! ! !#a&&y       !s$* AA A Arc&g|] }|SrB).0r6r@s r z5WordpressWafBulkEndpoints.waf_set..s0&'q!!r'cg|]}|ddd S) succeededr4r(r5rB)rCr6s rrDz5WordpressWafBulkEndpoints.waf_set..s2kR@@r'c2g|]}|dd|ddS)r4skippedr5rHrB)rCss rrDz5WordpressWafBulkEndpoints.waf_set..s:6i1X;OOr'c2g|]}|dd|ddS)r4failedr5rHrB)rCfs rrDz5WordpressWafBulkEndpoints.waf_set..s:6h!H+NNr')itemsrFrJrM)rrSECURITY_PLUGIN_ENABLEDloggerwarningr0r HostingPanel get_usersr;listdictfromkeysrr<tuplerangelen_MAX_CONCURRENTasynciogather)r>r(r1r panel_usersr=rFrJrM valid_usersr6ibatchresultserrrOr@r?s` @@rwaf_setz!WordpressWafBulkEndpoints.waf_setMsL  *!A  KU]!"IJJ J  U !"=>> >0 !;   L        M$>$@$@$J$J$L$LLLLLLLMMKK   !9a99   "    P{++KKK]]5)) P P ##&&q))))NNA9M#N#NOOOOi'  !C !E#sTz/,B ! ! ! ! ! ! !q#k**O<< > >A+6q1;N7N+OE$NE2222222G! > >3;$$Q''''MM1"<"<====  >  "        "    s88B11 C;CCrr4rlimitoffsetc8K||dkrtd|dkrtdtj}t\}}} t t tj d{V} n%#t$r} td| | d} ~ wwxYw|&|t| vrt|d|g} | dtd{V|tnt|t} \Zt!| } t#| ||| z}| dt$|d{V}fd|D}ne| dt$| d{V}fd|D}|d t!|} |||| z}||rt(nt*| rt(nt*| |d S) Nrz--limit must be >= 0z--offset must be >= 0r3z is not a hosting userc0g|]}t|SrB)r&)rCrrs rrDz8WordpressWafBulkEndpoints.waf_status..s#DDD\#{33DDDr'cRg|]#}t|t|$SrB)r,r&)rCrrrr(s rrDz8WordpressWafBulkEndpoints.waf_status..sEC00S+..r'c|dS)NrrB)r`s rz6WordpressWafBulkEndpoints.waf_status..s QvYr')key)security_plugin_enabled global_wafglobal_waf_default total_countrO)rr\get_running_loopr rUrVrWrrSrTr;r0run_in_executorr _SAFETY_CAPminrZsortedrsortr#r$)r>r4r(rrerflooprmglobal_waf_enabledror^r= page_sizerppagerrOrs `` @rr!z$WordpressWafBulkEndpoints.waf_statuss  !"899 9 A::!"9:: :')) ! !  #    M$>$@$@$J$J$L$LLLLLLLMMKK   !9a99    3{++++%&E&E&EFFF&K 00 .        $)=KKc%6M6M >fn k**K+&&v0B'BCD--,dDEDDDtDDDEE--,kDE JJ..J / / /e**K&6I#556E(?#5K;K$6K;K&   sAB)) C 3CC )FN)NNNNr)__name__ __module__ __qualname__rAV_IM360SCOPErr<boolrUrVrdintr!rBr'rr.r.Js, NE T eU++ "& ] ] ] ] Cy4 ]  ] ] ] ,+] ~ T eX.. !!  L L DjL d L d L Tz L  L  L L L /.L L L r'r.)'__doc__r\loggingr defence360agent.contracts.configrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrdefence360agent.subsys.panelsrdefence360agent.utilsrdefence360agent.utils.configr defence360agent.wordpress.pluginr r )defence360agent.wordpress.site_repositoryr getLoggerr{rQr[r#r$rsrUr<rXrrrrVr&r,r.rBr'rrs&& 666666555555@@@@@@@@777777''''''666666  8 $ $    9  %S4Zs* +,      sC$Jc) * 9=c3h        sC$Jc) *  $J  $J     p p p p p p p p p p r'defence360agent/simple_rpc/__pycache__/wp_waf_bulk.cpython-311.pyc0000644000000000000000000002577300000000000022133 0ustar r_jz dZddlZddlZddlZddlmZddlmZddlm Z m Z ddl m Z ddl mZddlmZdd lmZmZdd lmZejeZd Zd Zd ZdZdeedeeee dze!effdZ"deee dze!efde#e e fde#fdZ$deee dze!efdedzdedzde!fdZ%Gdde Z&dS)z Bulk WAF set + status endpoints.N) Wordpress)ValidationError) RootEndpointsbind) hosting_panel)Scope) update_config)waf_global_snapshot#waf_status_and_source_for_user_sync)count_installed_sites_by_uid enableddisablediusersreturncg}|D]Z} tj|j}n#t$rd}YnwxYwt |\}}|||||f[|S)N)pwdgetpwnampw_uidKeyErrorr append)rrowsnameuidrsources [/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/simple_rpc/wp_waf_bulk.py_resolve_accounts_syncr"s D22 ,t$$+CC   CCC =dCC T301111 Ks " 11row site_countscb|\}}}}||rtnt|||ddS)Nr)r waf_statusrwp_sites)_STATUS_ENABLED_STATUS_DISABLEDget)rrrrrrs r _status_itemr&0sE"%D#w)0Foo6FOOC++   statusrc\|\}}}}|rtnt}|||krdS|||krdSdS)NFT)r#r$)rr(r_rsrcr!s r_matchesr,<sN Aq'3$+A1AJ jF22u cVmmu 4r'ceZdZejZeddd ddedede edzd e fd Z eddd dd edzdedzd edzde dzde d e f dZ dS)WordpressWafBulkEndpointszwordpress-pluginwafsetFNr( all_usersrrcXK|r|td|s|td||stdtjstdtd||| t t jd{V}n%#t$r}td||d}~wwxYwg}g}g}|rt|} nQg} t |D]4} | |vr| | | | dd5|d kd td tttdzfffd t!d t#| t$D]l} fd| | | t$zD} t'j| d{V} | D]5\} }|| | | | |d6mgd|Dd|Dd|D}||||dS)Nz/Specify either --all-users or --users, not bothz%Specify either --all-users or --usersz--users must not be emptyzNWordPress Security Plugin is disabled. Enable it before changing WAF settings.z>AUDIT wordpress-plugin.waf.set status=%r all_users=%r users=%r#Could not enumerate hosting users: zNot a hosting user)userreasonrurcK tjddii|d{V|dfS#t$r}|t|fcYd}~Sd}~wwxYw)N WORDPRESS waf_enabled)r4)r _sink Exceptionstr)r6eself waf_values r_apply_to_userz9WordpressWafBulkEndpoints.waf_set.._apply_to_users !#J =)"<= $w ! ! !#a&&y       !s$* AA A Arc&g|] }|SrB).0r6r@s r z5WordpressWafBulkEndpoints.waf_set..s0&'q!!r'cg|]}|ddd S) succeededr4r(r5rB)rCr6s rrDz5WordpressWafBulkEndpoints.waf_set..s2kR@@r'c2g|]}|dd|ddS)r4skippedr5rHrB)rCss rrDz5WordpressWafBulkEndpoints.waf_set..s:6i1X;OOr'c2g|]}|dd|ddS)r4failedr5rHrB)rCfs rrDz5WordpressWafBulkEndpoints.waf_set..s:6h!H+NNr')itemsrFrJrM)rrSECURITY_PLUGIN_ENABLEDloggerwarningr0r HostingPanel get_usersr;listdictfromkeysrr<tuplerangelen_MAX_CONCURRENTasynciogather)r>r(r1r panel_usersr=rFrJrM valid_usersr6ibatchresultserrrOr@r?s` @@rwaf_setz!WordpressWafBulkEndpoints.waf_setMsL  *!A  KU]!"IJJ J  U !"=>> >0 !;   L        M$>$@$@$J$J$L$LLLLLLLMMKK   !9a99   "    P{++KKK]]5)) P P ##&&q))))NNA9M#N#NOOOOi'  !C !E#sTz/,B ! ! ! ! ! ! !q#k**O<< > >A+6q1;N7N+OE$NE2222222G! > >3;$$Q''''MM1"<"<====  >  "        "    s88B11 C;CCrr4rlimitoffsetc8K||dkrtd|dkrtdtj}t\}}} t t tj d{V} n%#t$r} td| | d} ~ wwxYw|&|t| vrt|d|g} | dtd{V|tnt|t} \Zt!| } t#| ||| z}| dt$|d{V}fd|D}ne| dt$| d{V}fd|D}|d t!|} |||| z}||rt(nt*| rt(nt*| |d S) Nrz--limit must be >= 0z--offset must be >= 0r3z is not a hosting userc0g|]}t|SrB)r&)rCrrs rrDz8WordpressWafBulkEndpoints.waf_status..s#DDD\#{33DDDr'cRg|]#}t|t|$SrB)r,r&)rCrrrr(s rrDz8WordpressWafBulkEndpoints.waf_status..sEC00S+..r'c|dS)NrrB)r`s rz6WordpressWafBulkEndpoints.waf_status..s QvYr')key)security_plugin_enabled global_wafglobal_waf_default total_countrO)rr\get_running_loopr rUrVrWrrSrTr;r0run_in_executorr _SAFETY_CAPminrZsortedrsortr#r$)r>r4r(rrerflooprmglobal_waf_enabledror^r= page_sizerppagerrOrs `` @rr!z$WordpressWafBulkEndpoints.waf_statuss  !"899 9 A::!"9:: :')) ! !  #    M$>$@$@$J$J$L$LLLLLLLMMKK   !9a99    3{++++%&E&E&EFFF&K 00 .        $)=KKc%6M6M >fn k**K+&&v0B'BCD--,dDEDDDtDDDEE--,kDE JJ..J / / /e**K&6I#556E(?#5K;K$6K;K&   sAB)) C 3CC )FN)NNNNr)__name__ __module__ __qualname__rAV_IM360SCOPErr<boolrUrVrdintr!rBr'rr.r.Js, NE T eU++ "& ] ] ] ] Cy4 ]  ] ] ] ,+] ~ T eX.. !!  L L DjL d L d L Tz L  L  L L L /.L L L r'r.)'__doc__r\loggingr defence360agent.contracts.configrdefence360agent.rpc_toolsr defence360agent.rpc_tools.lookuprrdefence360agent.subsys.panelsrdefence360agent.utilsrdefence360agent.utils.configr defence360agent.wordpress.pluginr r )defence360agent.wordpress.site_repositoryr getLoggerr{rQr[r#r$rsrUr<rXrrrrVr&r,r.rBr'rrs&& 666666555555@@@@@@@@777777''''''666666  8 $ $    9  %S4Zs* +,      sC$Jc) * 9=c3h        sC$Jc) *  $J  $J     p p p p p p p p p p r'defence360agent/simple_rpc/advisor.py0000644000000000000000000000277100000000000014733 0ustar from collections import defaultdict from defence360agent.contracts.config import ConfigFile from defence360agent.rpc_tools.lookup import RootEndpoints from defence360agent.utils.config import update_config from defence360agent.rpc_tools import lookup from defence360agent.api.server.events import EventsAPI from defence360agent.feature_management.checkers import config_cleanup class AdvisorEndpoints(RootEndpoints): @lookup.bind("advisor", "apply") async def advisor_apply(self, advices): return await self._apply(advices) @lookup.bind("advisor", "apply-all") async def apply_all(self): advices = await EventsAPI.advices() return await self._apply(advices) async def _apply(self, advices): target_conf = defaultdict(dict) current_conf = ConfigFile().config_to_dict() for advise in advices: self._extract_conf_from_advise(advise, current_conf, target_conf) await update_config(self._sink, target_conf) return {"items": config_cleanup(ConfigFile().config_to_dict())} @staticmethod def _extract_conf_from_advise(advise, current_conf, target_conf): for section_key, section_value in advise["ignore"].items(): for value_key, ignored_values in section_value.items(): if current_conf[section_key][value_key] in ignored_values: return for section_key, section_value in advise["config_action"].items(): target_conf[section_key].update(section_value) defence360agent/simple_rpc/analyst_cleanup.py0000644000000000000000000002170600000000000016445 0ustar import warnings from logging import getLogger from datetime import datetime, timedelta from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import RootEndpoints, bind import defence360agent.subsys.panels.hosting_panel as hp from defence360agent.utils.sshutil import ( get_ssh_port, check_ssh_connection, install_pub_key, ) from defence360agent.model.analyst_cleanup import AnalystCleanupRequest from defence360agent.api.server.analyst_cleanup import ( NO_AGENT_TOKEN, AnalystCleanupAPI, ) logger = getLogger(__name__) PREPARE_SERVER_GUIDE = "https://cloudlinux.zendesk.com/hc/en-us/articles/6245743410460-How-to-authenticate-your-server-for-Support-Team-and-use-the-SSH-access-form" ZENDESK_REGISTRATION_URL = ( "https://cloudlinux.zendesk.com/auth/v2/login/registration" ) NOT_ALLOWLISTED_MESSAGE = ( "You are not authorized to submit Analyst Cleanup requests." " Contact sales@cloudlinux.com to get access" ) _NOT_AUTHENTICATED_MESSAGE = ( "This server could not authenticate with the Imunify360 API." " Make sure the agent is registered and its license is active." ) _UNKNOWN_RESPONSE_MESSAGE = ( "Our support system returned an unexpected response." " Check your email for a ticket confirmation before retrying." ) _TICKET_ERROR_MESSAGES = { "not_allowlisted": NOT_ALLOWLISTED_MESSAGE, "not_authorized": ( "This server is not linked to a CloudLinux customer account." " Make sure its license is active and try again." ), NO_AGENT_TOKEN: _NOT_AUTHENTICATED_MESSAGE, "zendesk_unreachable": ( "Our support system is temporarily unreachable." " Please try again in a few minutes." ), "zendesk_upstream_error": ( "Our support system rejected the request." " Please try again in a few minutes." ), "zendesk_suspended": ( "Our support system did not accept the request." " Please contact CloudLinux support directly." ), "zendesk_unknown_response": _UNKNOWN_RESPONSE_MESSAGE, } _TICKET_ERROR_MESSAGES_BY_STATUS = { 200: _UNKNOWN_RESPONSE_MESSAGE, 400: ( "The cleanup request was rejected as invalid." " Try again with a shorter message." ), 401: _NOT_AUTHENTICATED_MESSAGE, } _TICKET_ERROR_DEFAULT = "Failed to create support ticket" # Conditions the admin can act on themselves; not an agent fault, so they # must not reach the error reporter. _CLIENT_STATE_CODES = frozenset( {"not_allowlisted", "not_authorized", NO_AGENT_TOKEN} ) def _ticket_error_message(status, body): return _TICKET_ERROR_MESSAGES.get( body.get("message"), _TICKET_ERROR_MESSAGES_BY_STATUS.get(status, _TICKET_ERROR_DEFAULT), ) def _is_expected_client_state(status, body): if body.get("message") in _CLIENT_STATE_CODES: return True return status is not None and 400 <= status < 500 class AnalystCleanupEndpoints(RootEndpoints): async def _create_zendesk_ticket( self, email, subject, full_description, ) -> (str, str): """ Creates a Zendesk ticket and return link and id of the ticket On any error raises ValidationError, which would be added to RPC answer """ status, body = await AnalystCleanupAPI.create_ticket( email, subject, full_description, ) ticket = body.get("ticket") or {} if status == 200 and ticket.get("url") and ticket.get("id"): logger.info(f"Created ticket on url {ticket['url']}") return ticket["url"], str(ticket["id"]) log = ( logger.warning if _is_expected_client_state(status, body) else logger.error ) log("Failed to create support ticket: status=%s body=%s", status, body) raise ValidationError(_ticket_error_message(status, body)) @bind("analyst-cleanup", "request") async def request_cleanup(self, email, username, message): """Handle analyst cleanup request""" # Check active tickets if active_ticket := AnalystCleanupRequest.get_active_request_link( username ): raise ValidationError( "You already have an active request for cleaning this user." " If you have additional information, you may follow" f" the link and provide new data here: {active_ticket}" ) # Check if cleanup is allowed if not (await AnalystCleanupAPI.check_cleanup_allowed()): raise ValidationError(NOT_ALLOWLISTED_MESSAGE) email_status = await AnalystCleanupAPI.check_registered(email) # Check if email is registered if not email_status.get("result", False): raise ValidationError( f"{email_status.get('message', '')} Couldn't register" " your email in our Zendesk system. You can make it manually" f" by following the link {ZENDESK_REGISTRATION_URL} and then" " try sending the request again." ) if email_status.get("is_new", False): warnings.warn( "We’ve set up a Zendesk account for you! To complete your" " registration, check your email and click the “Reset" " Password” button." ) # Install public key key_installed = await install_pub_key(username) # Get SSH port and check connection ssh_port = await get_ssh_port() connection_ok = await check_ssh_connection(ssh_port) # Prepare ticket subject and description subject = "Analyst Cleanup Request" server_access = ( f"{hp.HostingPanel().get_server_ip()}:{ssh_port}/{username}" ) full_description = ( f"Username: {username}\n" f"Server Access: {server_access}\n\n" f"Customer Message:\n{message}\n\n" ) if not key_installed: warnings.warn("Support SSH public key is not installed", Warning) full_description += ( "\n\nWARNING: Not able to install analyst's public key\n" " Please make it manually by reffering to" f" {PREPARE_SERVER_GUIDE}\n and provide credentials " "to zendesk ticket" ) elif not connection_ok: warnings.warn("SSH connection test failed", Warning) full_description += ( "\n\nWARNING: SSH connection test failed. Please verify SSH" " access and refer to the access request form." ) # Create Zendesk ticket # In a case of no url|id # ValidationError is raised from _create_zendesk_ticket ticket_url, ticket_id = await self._create_zendesk_ticket( email, subject, full_description, ) # Store request in database AnalystCleanupRequest.create_request( username=username, zendesk_id=ticket_id, ticket_link=ticket_url, ) return {"items": {"ticket_url": ticket_url}} @bind("analyst-cleanup", "get-requests") async def request_status(self, username=None, limit=50, offset=0): """ Get status of analyst cleanup requests for all or a specific user Completed tickets will only be visible for 2 weeks after their last update """ # Get user's requests using the get_user_requests method from the model # This will return the most recent requests first (ordered by created_at desc) if username is None: requests = AnalystCleanupRequest.get_all_requests(limit, offset) else: requests = AnalystCleanupRequest.get_user_requests( username, limit, offset ) # If no requests found, return appropriate response if not requests or len(requests) == 0: return [] # Calculate the cutoff date (2 weeks ago) two_weeks_ago = datetime.utcnow() - timedelta(weeks=2) logger.info(f"Showing requests since {two_weeks_ago}") # Filter requests: show all except completed tickets older than 2 weeks filtered_requests = [ { "username": req.username, "ticket_url": req.ticket_link, "status": req.status, "created_at": str(datetime.timestamp(req.created_at)), "last_update": str(datetime.timestamp(req.last_updated)), "zendesk_id": req.zendesk_id, } for req in requests if req.status != "completed" or req.last_updated > two_weeks_ago ] logger.info(f"Got requests: {filtered_requests}") # Return the request details return filtered_requests @bind("analyst-cleanup", "is-allowed") async def is_allowed(self): is_allowed = await AnalystCleanupAPI.check_cleanup_allowed() return {"items": {"is_allowed": is_allowed}} defence360agent/simple_rpc/endpoints.py0000644000000000000000000003523500000000000015270 0ustar """ Here you enumerate rpc endpoints """ import asyncio import json import time from collections import deque from logging import getLogger from typing import Dict from defence360agent import files from defence360agent.api.jwt_issuer import JWTIssuer from defence360agent.api.newsfeed import NewsFeed from defence360agent.api.pam_auth import PamAuth from defence360agent.contracts import config, eula from defence360agent.contracts.config import ( ANTIVIRUS_MODE, Core as CoreConfig, ImmutableMerger, LocalConfig, MutableMerger, effective_user_config, int_from_envvar, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.internals.cln import CLN, CLNError, InvalidLicenseError from defence360agent.myimunify.billing import ( collect_billing_incompatibilities, get_license_type, ) from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import ( CommonEndpoints, RootEndpoints, bind, ) from defence360agent.simple_rpc import caller_uid_var from defence360agent.subsys.panels.base import PanelException from defence360agent.utils import ( IMUNIFY_PACKAGE_NAMES, CheckRunError, check_db, getpwnam, system_packages_info, ) from defence360agent.utils.config import update_config from defence360agent.utils.support import ZendeskAPIError, send_request from defence360agent.utils.whmcs import sync_billing_data from defence360agent.utils.doctor import get_doctor_key from defence360agent.subsys.panels import hosting_panel logger = getLogger(__name__) class ConfigEndpoints(CommonEndpoints): @bind("config", "show") async def config_show(self, user=None): full_conf = config.ConfigFile() if user: user_conf_dict = effective_user_config( full_conf, config.ConfigFile(user) ) return {"items": user_conf_dict} else: return {"items": full_conf.config_to_dict()} @bind("config", "show", "defaults") async def config_show_defaults(self): layer_paths = MutableMerger.get_layer_names() return { "items": { "mutable_config": MutableMerger(layer_paths).configs_to_dict(), "local_config": LocalConfig().config_to_dict(normalize=False), "immutable_config": ImmutableMerger( layer_paths ).configs_to_dict(), } } @bind("config", "update") async def config_update(self, items=None, data=None, user=None): # workaround for https://cloudlinux.atlassian.net/browse/DEF-3902 # TODO: remove items from method parameters if items: data = items[0] new_data = json.loads(data) logger.warning("AUDIT config.update user=%r data=%r", user, new_data) await update_config( self._sink, new_data, user, ) return await self.config_show(user) @bind("config", "patch") async def config_update_ui(self, data=None, user=None): logger.warning("AUDIT config.patch user=%r data=%r", user, data) await update_config(self._sink, data, user) return await self.config_show(user) @bind("config", "patch-many") async def config_update_many_ui(self, data=None, users=None): if users is None: users = [] logger.warning("AUDIT config.patch-many users=%r data=%r", users, data) for user in users: await update_config(self._sink, data, user) return {} @bind("config", "get-many") async def config_get_many_ui(self, users=None): if users is None: return {} result = {"items": {}} full_conf = config.ConfigFile() for user in users: user_conf_dict = effective_user_config( full_conf, config.ConfigFile(user) ) result["items"][user] = user_conf_dict return result # Defence-in-depth behind a UID-scoped socket; persistent state would be disproportionate. _LOGIN_PAM_MAX = 5 _LOGIN_PAM_WINDOW = 60.0 _LOGIN_PAM_MAX_TRACKED = 10_000 _login_pam_failures: Dict[str, deque] = {} _LOGIN_PAM_UID_MAX = int_from_envvar("I360_LOGIN_PAM_UID_MAX", 300) _LOGIN_PAM_UID_WINDOW = 60.0 _LOGIN_PAM_UID_MAX_TRACKED = 10_000 _login_pam_uid_failures: Dict[int, deque] = {} def _login_pam_allowed(username: str, now: float) -> bool: history = _login_pam_failures.get(username) if history is None: return True cutoff = now - _LOGIN_PAM_WINDOW while history and history[0] < cutoff: history.popleft() if not history: del _login_pam_failures[username] return True return len(history) < _LOGIN_PAM_MAX def _login_pam_sweep(now: float) -> None: cutoff = now - _LOGIN_PAM_WINDOW stale = [u for u, h in _login_pam_failures.items() if h[-1] < cutoff] for username in stale: del _login_pam_failures[username] def _login_pam_record_failure(username: str, now: float) -> None: if ( username not in _login_pam_failures and len(_login_pam_failures) >= _LOGIN_PAM_MAX_TRACKED ): _login_pam_sweep(now) if len(_login_pam_failures) >= _LOGIN_PAM_MAX_TRACKED: del _login_pam_failures[next(iter(_login_pam_failures))] _login_pam_failures.setdefault(username, deque()).append(now) def _login_pam_reset(username: str) -> None: _login_pam_failures.pop(username, None) def _login_pam_uid_allowed(uid: int, now: float) -> bool: if _LOGIN_PAM_UID_MAX <= 0: return True history = _login_pam_uid_failures.get(uid) if history is None: return True cutoff = now - _LOGIN_PAM_UID_WINDOW while history and history[0] < cutoff: history.popleft() if not history: del _login_pam_uid_failures[uid] return True return len(history) < _LOGIN_PAM_UID_MAX def _login_pam_uid_sweep(now: float) -> None: cutoff = now - _LOGIN_PAM_UID_WINDOW stale = [u for u, h in _login_pam_uid_failures.items() if h[-1] < cutoff] for uid in stale: del _login_pam_uid_failures[uid] def _login_pam_uid_record_failure(uid: int, now: float) -> None: if _LOGIN_PAM_UID_MAX <= 0: return if ( uid not in _login_pam_uid_failures and len(_login_pam_uid_failures) >= _LOGIN_PAM_UID_MAX_TRACKED ): _login_pam_uid_sweep(now) if len(_login_pam_uid_failures) >= _LOGIN_PAM_UID_MAX_TRACKED: del _login_pam_uid_failures[next(iter(_login_pam_uid_failures))] _login_pam_uid_failures.setdefault(uid, deque()).append(now) class LoginEndpoints(CommonEndpoints): @bind("login", "pam") async def login_via_pam(self, username, password): now = time.monotonic() try: caller_uid = caller_uid_var.get() except LookupError: logger.error("AUDIT login.pam REJECTED: caller_uid_var unset") raise RuntimeError("login.pam reached without caller_uid_var set") if caller_uid != 0 and not _login_pam_uid_allowed(caller_uid, now): logger.warning("AUDIT login.pam RATE_LIMITED uid=%r", caller_uid) raise ValidationError("Authentication rate limit exceeded") if not _login_pam_allowed(username, now): logger.warning( "AUDIT login.pam RATE_LIMITED username=%r", username ) raise ValidationError("Authentication rate limit exceeded") pam_auth = PamAuth() authenticated = pam_auth.authenticate(username, password) if not authenticated: _login_pam_record_failure(username, now) if caller_uid != 0: _login_pam_uid_record_failure(caller_uid, now) logger.warning("AUDIT login.pam FAILED username=%r", username) raise ValidationError("Authentication failed") _login_pam_reset(username) logger.info("AUDIT login.pam SUCCESS username=%r", username) return { "items": JWTIssuer().get_token( username, await pam_auth.get_user_type(username) ) } class RootLoginEndpoints(RootEndpoints): @bind("login", "get") async def login_get(self, username): if not getpwnam(username): raise ValidationError("User name not found") return { "items": JWTIssuer().get_token( username, await PamAuth().get_user_type(username) ) } class PackageVersionsEndpoints(CommonEndpoints): @bind("get-package-versions") async def get_package_versions(self, user=None): return {"items": await system_packages_info(IMUNIFY_PACKAGE_NAMES)} class NewsEndpoints(RootEndpoints): @bind("get-news") async def get_news(self): return {"items": await NewsFeed.get()} class Endpoints(RootEndpoints): license_info = LicenseCLN.license_info @bind("register") async def register(self, regkey=None): LicenseCLN.get_token.cache_clear() if LicenseCLN.is_registered(): if LicenseCLN.is_valid(): if not ANTIVIRUS_MODE: raise ValidationError("Agent is already registered") else: logger.info( "Unregistering invalid license: %s" % LicenseCLN.get_token() ) await self.unregister() try: await CLN.register(regkey) except InvalidLicenseError as e: raise ValidationError(str(e)) except CLNError as e: logger.warning( "Can't register %r as imunify360 key. Trying to " "register it as a web panel key instead", regkey, ) try: await CLN.register( await hosting_panel.HostingPanel().retrieve_key() ) except NotImplementedError: logger.warning( "Registration with web panel's key doesn't supported" ) raise ValidationError(str(e)) except PanelException as panel_e: raise ValidationError("{}, {}".format(str(e), str(panel_e))) except (CLNError, InvalidLicenseError) as e: raise ValidationError(str(e)) return {} @bind("unregister") async def unregister(self): if not LicenseCLN.is_registered(): raise ValidationError("Agent is not registered yet") if LicenseCLN.is_free(): raise ValidationError("Free license can not be unregistered") await CLN.unregister() return {} @bind("update-license") async def update_license(self): if not LicenseCLN.is_registered(): raise ValidationError("Unregistered (server-id is not assigned)") token = LicenseCLN.get_token() LicenseCLN.users_count = ( await hosting_panel.HostingPanel().users_count() ) new_token = await CLN.refresh_token(token) if new_token is None: raise ValidationError("License does not exist. Agent unregistered") return {} @bind("rstatus") async def rstatus(self, paid=False): LicenseCLN.get_token.cache_clear() if not LicenseCLN.is_valid(): raise ValidationError("License is invalid for current server") if paid and LicenseCLN.is_free(): raise ValidationError("Free license") return self.license_info() @bind("version") async def version(self): return {"items": CoreConfig.VERSION} @bind("wakeup") async def wakeup(self): """Wake up the agent, so it can process the request, if it's sleeping""" return {} @bind("update") async def update_files( self, subj=None, force=False, list=False, version="latest" ): if subj and subj in config.FilesUpdate.DISABLED: if list: return files.Index(subj).get_list() if version: return await files.Index(subj).update_to(version, force) else: if list or version != "latest": raise ValidationError( "Listing and version are not supported for this files type" ) try: await files.update(subj, force) except (asyncio.TimeoutError, files.UpdateError): pass # the error has been logged in files.update already @bind("eula", "accept") async def eula_accept(self): await eula.accept() @bind("eula", "show") async def eula_show(self): return eula.text() @bind("checkdb") async def checkdb(self, recreate_schema=False): """Check DB consistency and repair if needed. If recreate_schema is set recreate schema for attached DB.""" if recreate_schema: check_db.recreate_schema() else: check_db.check_and_repair() @bind("doctor") async def doctor(self): key = await get_doctor_key() return ( "Please, provide this key:\n%s\nto Imunify360 Support Team\n" % key ) @bind("support", "send") async def send_to_support( self, email, subject, description, cln=None, attachments=None ): # Generating doctor and extracting key from output try: doctor_key = await get_doctor_key() except CheckRunError: doctor_key = None # Sending request via Zendesk API # https://developer.zendesk.com/rest_api/docs/core/requests#anonymous-requests try: ticket_url = await send_request( email, subject, description, doctor_key, cln, attachments ) except ZendeskAPIError as e: logger.error( "Got error from Zendesk API. error=%s, description=%s," " details=%s", e.error, e.description, e.details, ) raise return {"items": [ticket_url]} class WhmcsEndpoint(RootEndpoints): """ Describes all endpoints for interaction with WHMCS """ # needed by WHMCS to know whether it is compatible VERSION = "1" @bind("billing", "sync") async def billing_sync(self, data): try: decoded_data = json.loads(data) except json.JSONDecodeError: raise ValueError("Invalid JSON") result = await sync_billing_data(self._sink, decoded_data) return {"result": "success", "data": result} @bind("billing", "get-config") async def billing_get_config(self): result = dict( version=self.VERSION, billing_license=get_license_type(), issues=await collect_billing_incompatibilities(), ) return {"result": "success", "data": result} defence360agent/simple_rpc/hooks.py0000644000000000000000000000551000000000000014401 0ustar import json import logging from defence360agent.contracts.config import HookEvents from defence360agent.contracts.hooks import HooksConfig from defence360agent.contracts.license import LicenseCLN from defence360agent.model.event_hook import EventHook from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import RootEndpoints, bind from defence360agent.subsys import notifier logger = logging.getLogger(__name__) class HooksEndpoints(RootEndpoints): def _check_event(self, event, extra=None): if event not in HookEvents.EVENTS and event != extra: raise ValidationError( '"{}" is not valid event for hook'.format(event) ) @bind("hook", "add") async def hook_add(self, event, path): self._check_event(event) result = EventHook.add_hook(event=event, path=path) if not result: raise ValidationError( 'Unable to add hook "{} {}"'.format(event, path) ) result["status"] = "registered" return {"items": result} @bind("hook", "delete") async def hook_delete(self, event, path): self._check_event(event) result = EventHook.delete_hook(event=event, path=path) if not result: raise ValidationError( 'Unable to delete hook "{} {}"'.format(event, path) ) result["status"] = "unregistered" return {"items": result} @bind("hook", "list") async def hook_list(self, event): self._check_event(event, "all") result = EventHook.list_events(event) return {"items": result} @bind("hook", "add-native") async def hook_add_native(self, event, path): self._check_event(event) result = EventHook.add_hook(event=event, path=path, native=True) if not result: raise ValidationError( 'Unable to add native hook "{} {}"'.format(event, path) ) result["status"] = "registered" return {"items": result} @bind("notifications-config", "show") async def show(self): return {"items": HooksConfig().get()} @bind("notifications-config", "update") async def update(self, items=None, data=None): if LicenseCLN.is_demo(): raise ValidationError("This action is not allowed in demo version") if items: data = items[0] new_data = json.loads(data) HooksConfig().update(new_data) await notifier.config_updated() return await self.show() @bind("notifications-config", "patch") async def update_ui(self, data=None): if LicenseCLN.is_demo(): raise ValidationError("This action is not allowed in demo version") HooksConfig().update(data) await notifier.config_updated() return await self.show() defence360agent/simple_rpc/hosting_panel.py0000644000000000000000000000274500000000000016117 0ustar from defence360agent.subsys.panels.base import PanelException from defence360agent.subsys.panels.directadmin import DirectAdmin from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import RootEndpoints, bind class HostingPanelEndpoints(RootEndpoints): @bind("enable-plugin") async def enable_plugin(self, plugin_name=None): return await self.hosting_panel.enable_imunify_plugin(plugin_name) @bind("disable-plugin") async def disable_plugin(self, plugin_name=None): return await self.hosting_panel.disable_imunify_plugin(plugin_name) @bind("add-sudouser") async def add_sudouser(self, user): hp = self.hosting_panel if not isinstance(hp, DirectAdmin): raise ValidationError("Feature available only for DirectAdmin") return await hp.add_sudouser(user) @bind("delete-sudouser") async def delete_sudouser(self, user): hp = self.hosting_panel if not isinstance(hp, DirectAdmin): raise ValidationError("Feature available only for DirectAdmin") return await hp.delete_sudouser(user) @bind("list-docroots") async def get_docroots(self): return {"items": await self.hosting_panel.list_docroots()} @property def hosting_panel(self): try: return HostingPanel() except PanelException as e: raise ValidationError(str(e)) defence360agent/simple_rpc/myimunify.py0000644000000000000000000000531500000000000015307 0ustar import urllib.parse from typing import List, Optional import defence360agent.subsys.panels.hosting_panel as hp from defence360agent.contracts.config import ( MyImunifyConfig, is_mi_freemium_license, ) from defence360agent.myimunify.model import ( MyImunify, set_protection_status_for_all_users, update_users_protection, ) from defence360agent.rpc_tools import lookup from defence360agent.utils import Scope class MyImunifyEndpoints(lookup.RootEndpoints): SCOPE = Scope.IM360 @lookup.bind("myimunify", "update") async def update(self, items: List[str], protection: str): await update_users_protection( self._sink, items, protection == "enabled" ) return {} @lookup.bind("myimunify", "enable-all") async def enable_all(self): await set_protection_status_for_all_users(self._sink, True) @lookup.bind("myimunify", "disable-all") async def disable_all(self): await set_protection_status_for_all_users(self._sink, False) class MyImunifyCommonEndpoints(lookup.CommonEndpoints): SCOPE = Scope.IM360 @lookup.bind("myimunify", "status") async def status(self, items: List[str], user: Optional[str] = None): purchase_url = MyImunifyConfig.PURCHASE_PAGE_URL panel_manager = hp.HostingPanel() if user is not None: items = [user] # if MY_IMNUNIFY is disabled, we don't need to generate purchase # url with domain and ip [because it will not been shown to user] if MyImunifyConfig.ENABLED: user_domains = ( await panel_manager.get_domains_per_user() ).get(user, []) domain = next(iter(user_domains), None) purchase_url = ( MyImunifyConfig.PURCHASE_PAGE_URL + "/?" + urllib.parse.urlencode( { "m": "cloudlinux_advantage", "action": "provisioning", "suite": "my_imunify_account_protection", "username": user, "domain": domain, "server_ip": panel_manager.get_server_ip(), } ) ) response = MyImunify.select().where(MyImunify.user.in_(items)).dicts() return { "myimunify_enabled": MyImunifyConfig.ENABLED, "purchase_page_url": purchase_url, "is_freemium": is_mi_freemium_license(), "items": [ {"username": item["user"], "protection": item["protection"]} for item in response ], } defence360agent/simple_rpc/permissions.py0000644000000000000000000000047500000000000015636 0ustar from defence360agent.contracts.permissions import permissions_list from defence360agent.rpc_tools.lookup import CommonEndpoints, bind class PermissionEndpoints(CommonEndpoints): @bind("permissions", "list") async def permissions_list(self, user=None): return {"items": await permissions_list(user)} defence360agent/simple_rpc/plesk_stats.py0000644000000000000000000001106500000000000015614 0ustar import datetime import json from contextlib import suppress from defence360agent.contracts.license import LicenseCLN from defence360agent.rpc_tools.lookup import RootEndpoints, bind from defence360agent.rpc_tools.utils import run_in_executor_decorator from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.subsys.panels.plesk.api import list_docroots_domains_users from defence360agent.utils import atomic_rewrite from defence360agent.subsys.panels.plesk import Plesk from defence360agent.subsys.features import kernel_care from defence360agent.utils import importer MalwareHit = importer.get( module="imav.malwarelib.model", name="MalwareHit", default=None ) class PleskStatsEndpoints(RootEndpoints): MAX_DOMAINS_COUNT = 100 @bind("plesk-stats") async def plesk_stats(self): panel = HostingPanel() assert isinstance(panel, Plesk), "only for plesk" current_timestamp = int(round(datetime.datetime.now().timestamp())) last_modified_str = str( datetime.datetime.fromtimestamp( current_timestamp, datetime.timezone.utc, ) ) domains_stats = await self._domains_stats( await list_docroots_domains_users(), ) return { "items": { "last_modified": current_timestamp * 1000, "last_modified_str": last_modified_str, **domains_stats, **(await self._get_stats_field_in_plugin_info()), "license": (1 if LicenseCLN.is_valid() else 0), } } @classmethod async def _get_stats_field_in_plugin_info(cls): if not await kernel_care.KernelCare().check_installed(): return {} plugin_info = await kernel_care.KernelCare().get_plugin_info() previous = { "effective_kernel": None, "first_time_update_available": None, } with suppress(FileNotFoundError): with open(kernel_care.KernelCare.KC_PROPERTIES) as file: previous = json.load(file) update_available = plugin_info["updateCode"] == "1" first_time_update_available = ( datetime.datetime.now(tz=datetime.timezone.utc) if plugin_info["effectiveKernel"] != previous["effective_kernel"] else datetime.datetime.fromtimestamp( previous["first_time_update_available"], datetime.timezone.utc ) ) outdated_since_days = ( 0 if not update_available else ( datetime.datetime.now(tz=datetime.timezone.utc) - first_time_update_available ).days ) atomic_rewrite( kernel_care.KernelCare.KC_PROPERTIES, json.dumps( { "effective_kernel": plugin_info["effectiveKernel"], "first_time_update_available": first_time_update_available.timestamp(), # noqa } ), backup=False, ) return { "kernel_uptodate": plugin_info["autoUpdate"], "outdated_since_days": outdated_since_days, } @run_in_executor_decorator def _domains_stats(self, plesk_response): if MalwareHit is None: return { "infected_sites": [], "wsites_infected": 0, } file_names = list( MalwareHit.select(MalwareHit.orig_file) .where(MalwareHit.is_infected()) .tuples() ) # usually infected_users << total_users (according to ch) # so we can compare each hit only with docroots, whose owners are # marked as infected in imunify database infected_users = set( data[0] for data in list( MalwareHit.select(MalwareHit.user) .where(MalwareHit.is_infected()) .distinct() .tuples() ) ) infected_plesk_response = list( filter( lambda data: data[2] in infected_users, plesk_response, ) ) infected_sites = [] for docroot, domain, user in infected_plesk_response: for (filename,) in file_names: if filename.startswith(docroot): infected_sites.append(domain) break return { "infected_sites": infected_sites[: self.MAX_DOMAINS_COUNT], "wsites_infected": len(infected_sites), } defence360agent/simple_rpc/reputation_management.py0000644000000000000000000000371100000000000017645 0ustar import logging from defence360agent.rpc_tools import lookup from defence360agent.rpc_tools.validate import ( ValidationError, validate_av_plus_license, ) from defence360agent.subsys.panels.base import PanelException from defence360agent.model.infected_domain import InfectedDomainList from defence360agent.subsys.panels import hosting_panel from defence360agent.api.server.reputation import ReputationAPI from defence360agent.model.simplification import run_in_executor logger = logging.getLogger(__name__) class ReputationManagementEndpoints(lookup.RootEndpoints): @lookup.bind("infected-domains") @validate_av_plus_license async def list_domains(self, limit, offset): existing_users = set(await hosting_panel.HostingPanel().get_users()) items, max_count = InfectedDomainList.get_by_user( existing_users, offset=offset, limit=limit ) return { "items": items, "max_count": max_count, } @lookup.bind("check-domains") @validate_av_plus_license async def check_domains(self): hp = hosting_panel.HostingPanel() # TODO: strange behaviour is detected # I think it's normal case for cPanel DNS only # we should do not process domains if it not found or panel # not available if not hp.is_installed(): raise ValidationError("No avaliable control panel found!") try: domains = await hp.get_user_domains() except PanelException as e: raise ValidationError(str(e)) if not domains: raise ValidationError("Domains not found") reputation_data = await ReputationAPI.check(domains) domain_to_user = ( await hosting_panel.HostingPanel().get_domain_to_owner() ) await run_in_executor( None, lambda: InfectedDomainList.refresh_domains( reputation_data, domain_to_user ), ) defence360agent/simple_rpc/schema/0000755000000000000000000000000000000000000014143 5ustar defence360agent/simple_rpc/schema.py0000644000000000000000000001776500000000000014535 0ustar from cerberus.errors import BaseErrorHandler, BasicErrorHandler from cerberus.schema import DefinitionSchema, UnvalidatedSchema from defence360agent.contracts.config import UserType from defence360agent.rpc_tools.middleware import ( add_eula, add_license, add_license_user, add_version, collect_warnings, counts, default_to_items, max_count, preserve_remote_addr, resolve_caller_panel_login, send_command_invoke_message, set_caller_type_context, ) from defence360agent.rpc_tools.utils import prepare_schema class ErrorHandler(BaseErrorHandler): messages = BasicErrorHandler.messages.copy() def collect_errors(self, error): if error.child_errors: for err in error.child_errors: yield from self.collect_errors(err) else: # avoid abstract error: required field yield "field: '{}', value: '{}', error: {}".format( error.field, error.value, self.messages.get(error.code, "").format( *error.info, constraint=error.constraint, field=error.field, value=error.value ), ) def __call__(self, errors): string_representation = [] for error in errors: for info in self.collect_errors(error): string_representation.append(info) return string_representation def init_validator(schema_validator, validate_middleware, schema_paths): # Cerberus meta-validates any plain mapping handed to it as a schema, and # then re-hashes it on every normalized() call. Ours ships with the # package, so that is ~0.26s per process plus ~76ms per validated request # spent re-deriving one constant answer; unit tests check the schema # instead. expand() must stay - child validators get sub-schemas as-is. _validator = schema_validator( UnvalidatedSchema( DefinitionSchema.expand(prepare_schema(schema_paths)) ), error_handler=ErrorHandler, ) # NOTE: it is processed in the reversed order, see _apply_middleware _middleware = { None: [ # First entry = outermost wrapper, so the caller type is set # before validate_middleware runs the coerce functions. (set_caller_type_context, (UserType.ROOT, UserType.NON_ROOT)), # before send_command_invoke_message so CommandInvoke reports # the resolved login (resolve_caller_panel_login, (UserType.NON_ROOT,)), (send_command_invoke_message, (UserType.ROOT, UserType.NON_ROOT)), # validation before processing the data ( validate_middleware(_validator), (UserType.ROOT, UserType.NON_ROOT), ), # inject license for root (add_license, (UserType.ROOT,)), # inject license for regular user (add_license_user, (UserType.NON_ROOT,)), # inject eula (add_eula, (UserType.ROOT,)), # inject version (add_version, (UserType.ROOT, UserType.NON_ROOT)), # add warnings if any (collect_warnings, (UserType.ROOT, UserType.NON_ROOT)), # for backward compatibility (default_to_items, (UserType.ROOT, UserType.NON_ROOT)), ], ("whitelist", "ip", "list"): [ (counts, (UserType.ROOT, UserType.NON_ROOT)) ], ("blacklist", "ip", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("graylist", "ip", "list"): [ (counts, (UserType.ROOT, UserType.NON_ROOT)) ], ("whitelist", "ip", "add"): [ (preserve_remote_addr, (UserType.ROOT, UserType.NON_ROOT)) ], ("blacklist", "ip", "add"): [ (preserve_remote_addr, (UserType.ROOT, UserType.NON_ROOT)) ], ("whitelist", "country", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("blacklist", "country", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("graylist", "country", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("blacklist",): [(counts, (UserType.ROOT, UserType.NON_ROOT))], ("whitelist",): [(counts, (UserType.ROOT, UserType.NON_ROOT))], ("whitelisted-crawlers", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("blocked-port", "list"): [ (counts, (UserType.ROOT, UserType.NON_ROOT)) ], ("blocked-port-ip", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("rules", "list-disabled"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("wordpress-plugin", "rules", "list-disabled"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("wordpress-plugin", "list-sites"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("proactive", "ignore", "list"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("feature-management", "show"): [ (max_count, (UserType.ROOT, UserType.NON_ROOT)) ], ("ip-list", "synced"): [(counts, (UserType.ROOT, UserType.NON_ROOT))], ("ip-list", "local", "list"): [ (counts, (UserType.ROOT, UserType.NON_ROOT)) ], ("ip-list", "local", "add"): [ (preserve_remote_addr, (UserType.ROOT, UserType.NON_ROOT)) ], ("ip-list", "local", "delete"): [ (preserve_remote_addr, (UserType.ROOT, UserType.NON_ROOT)) ], } _middleware_exclude = { ("enable-plugin",): [add_eula], ("disable-plugin",): [add_eula], ("switch-max-webserver",): [add_eula], ("install-vendors",): [add_eula], ("uninstall-vendors",): [add_eula], ("add-sudouser",): [add_eula], ("delete-sudouser",): [add_eula], ("doctor",): [add_eula], ("captcha", "update-localizations"): [add_eula], ("captcha", "compile-localizations"): [add_eula], ("update",): [add_eula], ("kcarectl", "disable-auto-update"): [add_eula], ("kcarectl", "enable-auto-update"): [add_eula], ("kcarectl", "plugin-info"): [add_eula], ("register",): [add_eula], ("unregister",): [add_eula], ("rstatus",): [add_eula], ("update-license",): [add_eula], ("3rdparty", "list"): [add_eula], ("admin-emails",): [add_eula], ("list-docroots",): [add_eula], ("features", "list"): [add_eula], ("features", "status"): [add_eula], ("features", "install"): [add_eula], ("features", "remove"): [add_eula], ("feature-management", "native", "enable"): [add_eula], ("feature-management", "native", "disable"): [add_eula], ("feature-management", "native", "status"): [add_eula], ("import", "wblist"): [add_eula], ("rules", "update-app-specific-rules"): [add_eula], ("support", "send"): [add_eula], ("3rdparty", "conflicts"): [add_eula], ("smtp-blocking", "reset"): [add_eula], ("smtp-blocking", "sync"): [add_eula], ("malware", "on-demand", "check-detached"): [add_eula], ("checkdb",): [add_eula], ("restore-configs",): [add_eula], ("patchman", "users"): [add_eula], ("patchman", "register"): [add_eula], ("patchman", "install"): [add_eula], ("patchman", "migrate"): [add_eula], ("patchman", "uninstall"): [add_eula], ("patchman", "status"): [add_eula], ("patchman", "install", "realtime"): [add_eula], ("patchman", "uninstall", "realtime"): [add_eula], ("analyst-cleanup", "request"): [add_eula], ("analyst-cleanup", "get-requests"): [add_eula], ("analyst-cleanup", "is-allowed"): [add_eula], } return _validator, _middleware, _middleware_exclude defence360agent/simple_rpc/schema/advisor.pickle0000644000000000000000000000023200000000000017000 0ustar }(advisor apply-all}(help (internal)cli}users]rootasu advisor apply}(help (internal)cli}users]rootasuu.defence360agent/simple_rpc/schema/advisor.yaml0000644000000000000000000000020700000000000016475 0ustar advisor apply-all: help: (internal) cli: users: - root advisor apply: help: (internal) cli: users: - root defence360agent/simple_rpc/schema/analyst-cleanup.pickle0000644000000000000000000000200000000000000020424 0ustar }(analyst-cleanup request}(help6Send request to malware remediation team of imunify360 return_typeAnalystCleanupRequestResponsetypedictcli}(users]roota require_rpcanyuschema}(email}(typestringregex[^@]+@[^@]+\.[^@]+$requireduusername}(typestringrequiredemptyumessage}(typestringrequiredemptyuuuanalyst-cleanup get-requests}(helpTGet analyst-cleanup requests for provided username or all if username isn't provided return_type!AnalystCleanupGetRequestsResponsetypedictcli}(users]roota require_rpcanyuschema}(username}(typestringrequiredemptyulimit}(typeintegercoerceintdefaultK2uoffset}(typeintegercoerceintdefaultKuuuanalyst-cleanup is-allowed}(help@Send request imunify360 API and shows is analyst-cleanup allowed return_typeAnalystCleanupAllowedResponsecli}(users]roota require_rpcanyuuu.defence360agent/simple_rpc/schema/analyst-cleanup.yaml0000644000000000000000000000211300000000000020124 0ustar analyst-cleanup request: help: "Send request to malware remediation team of imunify360" return_type: AnalystCleanupRequestResponse type: dict cli: users: - root require_rpc: any schema: email: type: string regex: '[^@]+@[^@]+\.[^@]+$' required: true username: type: string required: true empty: false message: type: string required: true empty: false analyst-cleanup get-requests: help: "Get analyst-cleanup requests for provided username or all if username isn't provided" return_type: AnalystCleanupGetRequestsResponse type: dict cli: users: - root require_rpc: any schema: username: type: string required: false empty: true limit: type: integer coerce: int default: 50 offset: type: integer coerce: int default: 0 analyst-cleanup is-allowed: help: "Send request imunify360 API and shows is analyst-cleanup allowed" return_type: AnalystCleanupAllowedResponse cli: users: - root require_rpc: any defence360agent/simple_rpc/schema/auth-cloud.pickle0000644000000000000000000000036100000000000017401 0ustar }( auth-cloud}( return_typeTokenAgentResponsehelpGet independent agent ID tokencli}users]rootasuauth-cloud-refresh-token}(help&Refresh the independent agent ID tokencli}users]rootasuu.defence360agent/simple_rpc/schema/auth-cloud.yaml0000644000000000000000000000033500000000000017075 0ustar auth-cloud: return_type: TokenAgentResponse help: Get independent agent ID token cli: users: - root auth-cloud-refresh-token: help: Refresh the independent agent ID token cli: users: - root defence360agent/simple_rpc/schema/billing.pickle0000644000000000000000000000111200000000000016747 0ustar ?}( billing sync}( return_typeWhmcsUpdateResponsecli_onlyhelp1(internal) For communication with whmcs updates. cli}users]rootastypedictschema}data}(typestringnullable positionalhelpConfig options to update, as a JSON-encoded string. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MY_IMUNIFY": {"protection": "disabled"}}` usubilling get-config}(cli_onlyhelp1(internal) For communication with whmcs updates. cli}users]rootastypedictuu.defence360agent/simple_rpc/schema/billing.yaml0000644000000000000000000000116700000000000016454 0ustar billing sync: return_type: WhmcsUpdateResponse cli_only: true help: | (internal) For communication with whmcs updates. cli: users: - root type: dict schema: data: type: string nullable: false positional: true help: | Config options to update, as a JSON-encoded string. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MY_IMUNIFY": {"protection": "disabled"}}` billing get-config: cli_only: true help: | (internal) For communication with whmcs updates. cli: users: - root type: dict defence360agent/simple_rpc/schema/checkdb.pickle0000644000000000000000000000027500000000000016723 0ustar }checkdb}(help (internal)cli}(users]roota require_rpcstoppedutypedictschema}recreate_schema}(typebooleandefaultrequiredusus.defence360agent/simple_rpc/schema/checkdb.yaml0000644000000000000000000000027700000000000016420 0ustar checkdb: help: (internal) cli: users: - root require_rpc: stopped type: dict schema: recreate_schema: type: boolean default: false required: false defence360agent/simple_rpc/schema/config.pickle0000644000000000000000000000641100000000000016603 0ustar  }( config update}( return_typeConfigAgentResponsehelpk(internal) Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`). cli}users]rootastypedictschema}(items}(typelistschema}typestringshelp (internal)udata}(typestringnullable positionalhelpConfig options to update, as a JSON-encoded string. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` uuser}(typestringnullablehelpAdmins can specify a user to update the config for. If not specified, and executed by admin, the config will be updated for root. If not specified, and executed by user, the config will be updated for that user. uuu config patch}( return_typeConfigAgentResponsehelp`Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`). cli}users]rootastypedictschema}(data}(typedictnullablehelpConfig options to update. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` uuser}(typestringnullablehelpAdmins can specify a user to update the config for. If not specified, and executed by admin, the config will be updated for root. If not specified, and executed by user, the config will be updated for that user. uuuconfig patch-many}(help1Update Imunify configuration for multiple users. cli}users]rootastypedictschema}(data}(typedictnullablehelpConfig options to update. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` uusers}(typelistschema}typestringsnullablehelpNList of users to update the config for. Example: `["user1", "user2", "root"]` uuuconfig get-many}( return_typeConfigAgentResponsehelp.Get Imunify configuration for multiple users. cli}users]rootastypedictschema}users}(typelistschema}typestringsnullablehelpIList of users to get the config for. Example: `"user1", "user2", "root"` usu config show}( return_typeConfigAgentResponsehelpGet Imunify configuration. This is the result of merging all config files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory. cli}users]rootastypedictschema}user}(typestringnullablehelpAdmins can specify whose config to get. If not specified, and executed by admin, returns the root config. If not specified, and executed by user, returns the config of that user. usuconfig show defaults}(helpXGet details on how the config is merged: - `mutable_config` - all files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory before `90-local.config`. They can be overridden via API. - `local_config` - `/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`, controlled by API. - `immutable_config` - all files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory after `90-local.config`. They cannot be overridden via API. cli}users]rootastypedictuu.defence360agent/simple_rpc/schema/config.yaml0000644000000000000000000000744400000000000016305 0ustar config update: return_type: ConfigAgentResponse help: | (internal) Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`). # FIXME: cli section required for UI tests cli: users: - root type: dict schema: # workaround for https://cloudlinux.atlassian.net/browse/DEF-3902 # TODO: remove items, make data not nullable items: type: list schema: type: string help: (internal) data: type: string nullable: true positional: true help: | Config options to update, as a JSON-encoded string. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` user: type: string nullable: true help: | Admins can specify a user to update the config for. If not specified, and executed by admin, the config will be updated for root. If not specified, and executed by user, the config will be updated for that user. config patch: return_type: ConfigAgentResponse help: | Update Imunify configuration (`/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`). # FIXME: cli section required for UI tests cli: users: - root type: dict schema: data: type: dict nullable: true help: | Config options to update. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` user: type: string nullable: true help: | Admins can specify a user to update the config for. If not specified, and executed by admin, the config will be updated for root. If not specified, and executed by user, the config will be updated for that user. config patch-many: help: | Update Imunify configuration for multiple users. cli: users: - root type: dict schema: data: type: dict nullable: true help: | Config options to update. Note: it doesn't have to be a full config, only the options that need to be updated. Example: `{"MALWARE_SCAN": {"enabled": true}}` users: type: list schema: type: string nullable: false help: | List of users to update the config for. Example: `["user1", "user2", "root"]` config get-many: return_type: ConfigAgentResponse help: | Get Imunify configuration for multiple users. cli: users: - root type: dict schema: users: type: list schema: type: string nullable: false help: | List of users to get the config for. Example: `"user1", "user2", "root"` config show: return_type: ConfigAgentResponse help: | Get Imunify configuration. This is the result of merging all config files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory. cli: users: - root type: dict schema: user: type: string nullable: true help: | Admins can specify whose config to get. If not specified, and executed by admin, returns the root config. If not specified, and executed by user, returns the config of that user. config show defaults: help: | Get details on how the config is merged: - `mutable_config` - all files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory before `90-local.config`. They can be overridden via API. - `local_config` - `/etc/sysconfig/imunify360/imunify360.config.d/90-local.config`, controlled by API. - `immutable_config` - all files in `/etc/sysconfig/imunify360/imunify360.config.d/` directory after `90-local.config`. They cannot be overridden via API. cli: users: - root type: dict defence360agent/simple_rpc/schema/conflicts.pickle0000644000000000000000000000020500000000000017315 0ustar z}3rdparty conflicts}(cli}(users]roota require_rpcanyuhelp#Shows conflicts with other softwareus.defence360agent/simple_rpc/schema/conflicts.yaml0000644000000000000000000000016600000000000017016 0ustar 3rdparty conflicts: cli: users: - root require_rpc: any help: "Shows conflicts with other software" defence360agent/simple_rpc/schema/doctor.pickle0000644000000000000000000000014300000000000016624 0ustar X}doctor}(help (internal)cli}(users]roota require_rpcdirectuus.defence360agent/simple_rpc/schema/doctor.yaml0000644000000000000000000000012200000000000016314 0ustar doctor: help: (internal) cli: users: - root require_rpc: direct defence360agent/simple_rpc/schema/eula.pickle0000644000000000000000000000026100000000000016261 0ustar }( eula accept}(help Accept EULA return_typeNullAgentResponsecli}users]rootasu eula show}(helpGet EULAcli}users]rootasuu.defence360agent/simple_rpc/schema/eula.yaml0000644000000000000000000000023500000000000015755 0ustar eula accept: help: Accept EULA return_type: NullAgentResponse cli: users: - root eula show: help: Get EULA cli: users: - root defence360agent/simple_rpc/schema/files.pickle0000644000000000000000000000044400000000000016440 0ustar }update}(help (internal)cli}(users]roota require_rpcanyutypedictschema}(subj}(typestring positionaluforce}(typebooleandefaultulist}(typebooleandefaultuversion}(typestringdefaultlatestuuus.defence360agent/simple_rpc/schema/files.yaml0000644000000000000000000000046700000000000016140 0ustar update: help: (internal) cli: users: - root require_rpc: any type: dict schema: subj: type: string positional: true force: type: boolean default: false list: type: boolean default: false version: type: string default: latest defence360agent/simple_rpc/schema/get-news.pickle0000644000000000000000000000020000000000000017055 0ustar u}get-news}(help (internal)cli}users]rootastypedict return_typeGetNewsAgentResponseus.defence360agent/simple_rpc/schema/get-news.yaml0000644000000000000000000000015500000000000016561 0ustar get-news: help: (internal) cli: users: - root type: dict return_type: GetNewsAgentResponse defence360agent/simple_rpc/schema/google-safe-engine.pickle0000644000000000000000000000066600000000000020777 0ustar }(infected-domains}(typedict return_typeReputationAgentResponsecli}users]rootashelpReturns infected domain listschema}(limit}(typeintegerdefaultK2coerceinthelpoffset for paginationuoffset}(typeintegerdefaultKcoerceinthelplimit for paginationuuu check-domains}(typedictcli}users]rootashelpSend domain list checkuu.defence360agent/simple_rpc/schema/google-safe-engine.yaml0000644000000000000000000000066700000000000020473 0ustar infected-domains: type: dict return_type: ReputationAgentResponse cli: users: - root help: Returns infected domain list schema: limit: type: integer default: 50 coerce: int help: offset for pagination offset: type: integer default: 0 coerce: int help: limit for pagination check-domains: type: dict cli: users: - root help: Send domain list checkdefence360agent/simple_rpc/schema/hook.pickle0000644000000000000000000000110300000000000016267 0ustar 8}( hook list}(typedictcli}users]rootasschema}event}(typestringrequiredusuhook add}(typedictcli}users]rootasschema}(event}(typestringrequiredupath}(typestringrequireduuu hook delete}(typedictcli}users]rootasschema}(event}(typestringrequiredupath}(typestringrequireduuuhook add-native}(typedictcli}users]rootasschema}(event}(typestringrequiredupath}(typestringrequireduuuu.defence360agent/simple_rpc/schema/hook.yaml0000644000000000000000000000115700000000000015773 0ustar hook list: type: dict cli: users: - root schema: event: type: string required: true hook add: type: dict cli: users: - root schema: event: type: string required: true path: type: string required: true hook delete: type: dict cli: users: - root schema: event: type: string required: true path: type: string required: true hook add-native: type: dict cli: users: - root schema: event: type: string required: true path: type: string required: truedefence360agent/simple_rpc/schema/hooks.pickle0000644000000000000000000000146200000000000016462 0ustar '}(notifications-config update}( return_typeConfigAgentResponsehelpS(internal) https://docs.imunify360.com/command_line_interface/#notifications-configcli}users]rootastypedictschema}(items}(typelistschema}typestringsudata}(typestringnullable positionaluuunotifications-config patch}( return_typeNotificationConfigAgentResponsehelpS(internal) https://docs.imunify360.com/command_line_interface/#notifications-configcli}users]rootastypedictschema}data}(typedictnullableusunotifications-config show}( return_typeNotificationConfigAgentResponsehelpS(internal) https://docs.imunify360.com/command_line_interface/#notifications-configcli}users]rootastypedictuu.defence360agent/simple_rpc/schema/hooks.yaml0000644000000000000000000000147400000000000016160 0ustar notifications-config update: return_type: ConfigAgentResponse help: (internal) https://docs.imunify360.com/command_line_interface/#notifications-config cli: users: - root type: dict schema: items: type: list schema: type: string data: type: string nullable: true positional: true notifications-config patch: return_type: NotificationConfigAgentResponse help: (internal) https://docs.imunify360.com/command_line_interface/#notifications-config cli: users: - root type: dict schema: data: type: dict nullable: false notifications-config show: return_type: NotificationConfigAgentResponse help: (internal) https://docs.imunify360.com/command_line_interface/#notifications-config cli: users: - root type: dict defence360agent/simple_rpc/schema/hosting-panel.pickle0000644000000000000000000000151400000000000020105 0ustar A}( enable-plugin}(help4(internal) Enable hosting panel plugin (if detected)typedictcli}(users]roota require_rpcdirectuschema} plugin_name}(typestringnullableusudisable-plugin}(help'(internal) Disable hosting panel plugintypedictcli}(users]roota require_rpcdirectuschema} plugin_name}(typestringnullableusu add-sudouser}(help (internal)typedictcli}(users]roota require_rpcdirectuschema}user}(typestringrequiredusudelete-sudouser}(help (internal)typedictcli}(users]roota require_rpcdirectuschema}user}(typestringrequiredusu list-docroots}(help'(internal) Get docroots for all domainscli}(users]roota require_rpcanyuuu.defence360agent/simple_rpc/schema/hosting-panel.yaml0000644000000000000000000000157700000000000017611 0ustar enable-plugin: help: (internal) Enable hosting panel plugin (if detected) type: dict cli: users: - root require_rpc: direct schema: plugin_name: type: string nullable: true disable-plugin: help: (internal) Disable hosting panel plugin type: dict cli: users: - root require_rpc: direct schema: plugin_name: type: string nullable: true # Need only for DA add-sudouser: help: (internal) type: dict cli: users: - root require_rpc: direct schema: user: type: string required: true # Need only for DA delete-sudouser: help: (internal) type: dict cli: users: - root require_rpc: direct schema: user: type: string required: true list-docroots: help: (internal) Get docroots for all domains cli: users: - root require_rpc: any defence360agent/simple_rpc/schema/login.pickle0000644000000000000000000000104200000000000016441 0ustar }( login pam}(helpaUses PAM to check the provided credential and returns a token for USERNAME if PASSWORD is correctcli}users]rootasschema}(username}(typestringrequiredemptyupassword}(typestringrequiredemptyenvvarPASSWORDuu return_typeTokenAgentResponseu login get}(help8Returns a token for USERNAME (must be executed by admin)cli}users]rootasschema}username}(typestringrequiredemptyus return_typeTokenAgentResponseuu.defence360agent/simple_rpc/schema/login.yaml0000644000000000000000000000111400000000000016134 0ustar login pam: help: Uses PAM to check the provided credential and returns a token for USERNAME if PASSWORD is correct cli: users: - root schema: username: type: string required: true empty: false password: type: string required: true empty: false envvar: 'PASSWORD' return_type: TokenAgentResponse login get: help: Returns a token for USERNAME (must be executed by admin) cli: users: - root schema: username: type: string required: true empty: false return_type: TokenAgentResponse defence360agent/simple_rpc/schema/package-versions.pickle0000644000000000000000000000021100000000000020567 0ustar ~}get-package-versions}( return_typeGetPackageVersionsAgentResponsehelp (internal)cli}users]rootasus.defence360agent/simple_rpc/schema/package-versions.yaml0000644000000000000000000000016700000000000020274 0ustar get-package-versions: return_type: GetPackageVersionsAgentResponse help: (internal) cli: users: - root defence360agent/simple_rpc/schema/permissions.pickle0000644000000000000000000000022600000000000017707 0ustar }permissions list}(help (internal)typedictcli}users]rootasschema}user}(typestringrequiredusus.defence360agent/simple_rpc/schema/permissions.yaml0000644000000000000000000000021600000000000017401 0ustar permissions list: help: (internal) type: dict cli: users: - root schema: user: type: string required: false defence360agent/simple_rpc/schema/plesk-stats.pickle0000644000000000000000000000015700000000000017611 0ustar d} plesk-stats}(cli}users]rootashelp)Return stats, required by plesk extensionus.defence360agent/simple_rpc/schema/plesk-stats.yaml0000644000000000000000000000014000000000000017274 0ustar plesk-stats: cli: users: - root help: "Return stats, required by plesk extension" defence360agent/simple_rpc/schema/registration.pickle0000644000000000000000000000124000000000000020043 0ustar }(rstatus}(cli}(users]roota require_rpcanyuhelpGet registration statusschema}paid}(typebooleandefaultusuregister}( return_typeNoItemsAndEulaAgentResponsecli}(users]roota require_rpcanyutypedicthelpRegister the agentschema}regkey}(envvarREG_KEYtypestringdefaultIPLisascii positionalhelp8Registration key or 'IPL' word (if you registered by IP)usu unregister}(cli}(users]roota require_rpcanyuhelpUnregister the agentuupdate-license}(cli}(users]roota require_rpcanyuhelpForce update licenseuu.defence360agent/simple_rpc/schema/registration.yaml0000644000000000000000000000127400000000000017545 0ustar rstatus: cli: users: - root require_rpc: any help: "Get registration status" schema: paid: type: boolean default: false register: return_type: NoItemsAndEulaAgentResponse cli: users: - root require_rpc: any type: dict help: "Register the agent" schema: regkey: envvar: "REG_KEY" type: string default: "IPL" isascii: true positional: true help: "Registration key or 'IPL' word (if you registered by IP)" unregister: cli: users: - root require_rpc: any help: "Unregister the agent" update-license: cli: users: - root require_rpc: any help: "Force update license" defence360agent/simple_rpc/schema/support.pickle0000644000000000000000000000067700000000000017062 0ustar } support send}(help"Contact support team of imunify360typedictcli}(users]roota require_rpcanyuschema}(email}(typestringregex[^@]+@[^@]+\.[^@]+$requiredusubject}(typestringrequiredemptyu description}(typestringrequiredemptyucln}typestrings attachments}(typelistschema}(typestringdefault]is_absolute_pathuuuus.defence360agent/simple_rpc/schema/support.yaml0000644000000000000000000000100300000000000016535 0ustar support send: help: "Contact support team of imunify360" type: dict cli: users: - root require_rpc: any schema: email: type: string regex: '[^@]+@[^@]+\.[^@]+$' required: true subject: type: string required: true empty: false description: type: string required: true empty: false cln: type: string attachments: type: list schema: type: string default: [] is_absolute_path: Truedefence360agent/simple_rpc/schema/version.pickle0000644000000000000000000000024300000000000017020 0ustar }(version}(helpGet Imunify Agent versioncli}users]rootasuwakeup}(helpWake up Imunify Agentcli}users]rootasuu.defence360agent/simple_rpc/schema/version.yaml0000644000000000000000000000022000000000000016506 0ustar version: help: Get Imunify Agent version cli: users: - root wakeup: help: Wake up Imunify Agent cli: users: - root defence360agent/simple_rpc/schema/wordpress.pickle0000644000000000000000000000547200000000000017374 0ustar / }(%wordpress-plugin install-on-new-sites}(helpEInstall Imunify Security plugin for WordPress on new WordPress sites.cli}users]rootasuwordpress-plugin tidy-up}(help`Tidy-up on WordPress sites where the Imunify Security plugin for WordPress was manually removed.cli}users]rootasuwordpress-plugin update}(helpxUpdates Imunify Security plugin for WordPress to the latest version on all WordPress sites where it's already installed.cli}users]rootasu#wordpress-plugin install-and-update}(helpInstall Imunify Security plugin for WordPress on new sites, tidy-up manually deleted plugins, and update existing installations. This combines install-on-new-sites, tidy-up, and update in a single atomic operation.cli}users]rootasuwordpress-plugin list-incidents}(helpList WordPress incidentstypedict return_type#WordpressIncidentsListAgentResponsecli}users]rootasschema}(user}(typestringnullableu site_search}(typestringnullablehelpFilter by site path u by_abuser_ip}(typestringnullablehelpFilter by abuser IP address uby_country_code}(typestringnullablehelpFilter by country code u by_domain}(typestringnullablehelpFilter by domain usearch}(typestringnullablehelp(Search by IP address, name, description usince}(typeintegercoerceintnullable check_with] timestampahelp5Show incidents after this unix timestamp (inclusive) uto}(typeintegercoerceintnullable check_with] timestampahelp6Show incidents before this unix timestamp (inclusive) uinclude_hidden}(typebooleandefaulthelpxInclude incidents whose rule has the internal TEST- prefix (hidden from the WordPress plugin admin UI). Default: false. uorder_by}(typelistnullableschema}(typeorder_bycoerceorder_byuhelpList of fields to order by, each followed by a `+` (ascending) or `-` (descending). Supported fields: timestamp, severity, domain, abuser. E.g. `["timestamp-","severity-"]` would order by timestamp descending and severity descending. ulimit}(typeintegercoerceintdefaultK2uoffset}(typeintegercoerceintdefaultKuuuwordpress-plugin list-sites}(help"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"$ref":"#/definitions/IAnalystCleanupAllowedResult"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"IAnalystCleanupAllowedResult":{"type":"object","properties":{"is_allowed":{"type":"boolean"}},"additionalProperties":false,"required":["is_allowed"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/AnalystCleanupGetRequestsResponse.json0000644000000000000000000000770400000000000026005 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ListResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ListResponseData":{"type":"object","properties":{"max_count":{"type":"number"},"items":{"type":"array","items":{"$ref":"#/definitions/IAnalystCleanupGetRequestsItem"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"IAnalystCleanupGetRequestsItem":{"type":"object","properties":{"username":{"type":"string"},"ticket_url":{"type":"string"},"status":{"$ref":"#/definitions/AnalystCleanupStatus"},"created_at":{"type":"string"},"last_update":{"type":"string"},"zendesk_id":{"type":"string"}},"additionalProperties":false,"required":["created_at","last_update","status","ticket_url","username","zendesk_id"]},"AnalystCleanupStatus":{"enum":["completed","in_progress","pending"],"type":"string"},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/AnalystCleanupRequestResponse.json0000644000000000000000000000710300000000000025153 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"$ref":"#/definitions/IAnalystCleanupRequestResult"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"IAnalystCleanupRequestResult":{"type":"object","properties":{"ticket_url":{"type":"string"}},"additionalProperties":false,"required":["ticket_url"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/ConfigAgentResponse.json0000644000000000000000000000535500000000000023052 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"type":"object","additionalProperties":{"type":"object","additionalProperties":{}}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"allOf":[{"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}}},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}}},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}}},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}}},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}}}}},"expiration":{"type":["null","number"]},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}}},{"type":"object","properties":{"expiration":{}}}]},{"allOf":[{"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]}}},{"type":"object","properties":{"expiration":{}}}]},{"allOf":[{"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]}}},{"type":"object","properties":{"expiration":{}}}]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}}},{"type":"null"}]}}},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementDefaultsAgentResponse.json0000644000000000000000000000704700000000000027110 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"$ref":"#/definitions/FeaturesStatus"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"FeaturesStatus":{"type":"object","properties":{"proactive":{"type":"boolean"},"av":{"type":"boolean"}},"additionalProperties":false,"required":["av","proactive"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementEditAgentResponse.json0000644000000000000000000001606600000000000026227 0ustar {"anyOf":[{"$ref":"#/definitions/FeaturesManagementEditDefaultsAgentResponse"},{"$ref":"#/definitions/FeaturesManagementEditUsersAgentResponse"}],"definitions":{"FeaturesManagementEditDefaultsAgentResponse":{"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData<\"succeed\">"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"]},"AgentResponseData<\"succeed\">":{"type":"object","properties":{"items":{"type":"string","enum":["succeed"]},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"},"FeaturesManagementEditUsersAgentResponse":{"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData<{succeeded:string[];failed:string[];}>"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"]},"AgentResponseData<{succeeded:string[];failed:string[];}>":{"type":"object","properties":{"items":{"type":"object","properties":{"succeeded":{"type":"array","items":{"type":"string"}},"failed":{"type":"array","items":{"type":"string"}}},"additionalProperties":false,"required":["failed","succeeded"]},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementGetAgentResponse.json0000644000000000000000000000723300000000000026055 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"$ref":"#/definitions/ClientFeatures"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"ClientFeatures":{"type":"object","properties":{"proactive":{"$ref":"#/definitions/ProactiveFeature"},"av":{"enum":["full","na","report"],"type":"string"}},"additionalProperties":false,"required":["av","proactive"]},"ProactiveFeature":{"enum":["full","log","na"],"type":"string"},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementListAgentResponse.json0000644000000000000000000000666200000000000026256 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData<(keyofFeaturesStatus)[]>"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData<(keyofFeaturesStatus)[]>":{"type":"object","properties":{"items":{"type":"array","items":{"enum":["av","proactive"],"type":"string"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementNativeStatusAgentResponse.json0000644000000000000000000000716100000000000027770 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"$ref":"#/definitions/NativeFeaturesManagementStatus"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"NativeFeaturesManagementStatus":{"type":"object","properties":{"supported":{"type":"boolean"},"enabled":{"type":"boolean"}},"additionalProperties":false,"required":["enabled","supported"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/FeaturesManagementShowAgentResponse.json0000644000000000000000000000762500000000000026263 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ListResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ListResponseData":{"type":"object","properties":{"max_count":{"type":"number"},"items":{"type":"array","items":{"$ref":"#/definitions/FeaturesManagementResponseItem"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"FeaturesManagementResponseItem":{"type":"object","properties":{"name":{"type":"string"},"domains":{"type":"array","items":{"type":"string"}},"features":{"$ref":"#/definitions/FeaturesStatus"}},"additionalProperties":false,"required":["domains","features","name"]},"FeaturesStatus":{"type":"object","properties":{"proactive":{"type":"boolean"},"av":{"type":"boolean"}},"additionalProperties":false,"required":["av","proactive"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/GetNewsAgentResponse.json0000644000000000000000000000715400000000000023220 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/definitions/NewsItem"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"NewsItem":{"type":"object","properties":{"title":{"type":"string"},"pubDate":{"type":"string"},"guid":{"type":"string"},"link":{"type":"string"}},"additionalProperties":false,"required":["guid","link","pubDate","title"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/GetPackageVersionsAgentResponse.json0000644000000000000000000000742600000000000025372 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"anyOf":[{"$ref":"#/definitions/PackageVersions"},{"type":"null"}]},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"PackageVersions":{"type":"object","additionalProperties":{"type":["null","string"]},"properties":{"imunify-ui":{"type":["null","string"]},"imunify-antivirus":{"type":["null","string"]},"imunify360-firewall":{"type":["null","string"]},"imunify-core":{"type":["null","string"]}},"required":["imunify-antivirus","imunify-core","imunify-ui","imunify360-firewall"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/NoItemsAndEulaAgentResponse.json0000644000000000000000000000650500000000000024453 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ResponseDataExceptItemsAndEula"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ResponseDataExceptItemsAndEula":{"type":"object","properties":{"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/NotificationConfigAgentResponse.json0000644000000000000000000001641600000000000025421 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"type":"object","properties":{"admin":{"type":"object","additionalProperties":{},"properties":{"default_emails":{"type":"array","items":{"type":"string"}},"notify_from_email":{"type":["null","string"]},"locale":{"type":"string"}},"required":["default_emails","notify_from_email"]},"rules":{"type":"object","properties":{"REALTIME_MALWARE_FOUND":{"additionalProperties":false,"type":"object","properties":{"ADMIN":{"type":"object","properties":{"admin_emails":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["admin_emails","enabled"]},"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"required":["SCRIPT"]},"USER_SCAN_STARTED":{"type":"object","properties":{"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"additionalProperties":false,"required":["SCRIPT"]},"CUSTOM_SCAN_STARTED":{"type":"object","properties":{"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"additionalProperties":false,"required":["SCRIPT"]},"USER_SCAN_FINISHED":{"type":"object","properties":{"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"additionalProperties":false,"required":["SCRIPT"]},"CUSTOM_SCAN_FINISHED":{"type":"object","properties":{"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"additionalProperties":false,"required":["SCRIPT"]},"CUSTOM_SCAN_MALWARE_FOUND":{"additionalProperties":false,"type":"object","properties":{"ADMIN":{"type":"object","properties":{"admin_emails":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["admin_emails","enabled"]},"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"required":["SCRIPT"]},"USER_SCAN_MALWARE_FOUND":{"additionalProperties":false,"type":"object","properties":{"ADMIN":{"type":"object","properties":{"admin_emails":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["admin_emails","enabled"]},"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"required":["SCRIPT"]},"SCRIPT_BLOCKED":{"additionalProperties":false,"type":"object","properties":{"ADMIN":{"type":"object","properties":{"admin_emails":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["admin_emails","enabled"]},"SCRIPT":{"type":"object","properties":{"scripts":{"type":"array","items":{"type":"string"}},"enabled":{"type":"boolean"},"period":{"type":"number"}},"additionalProperties":false,"required":["enabled","scripts"]}},"required":["SCRIPT"]}},"additionalProperties":false,"required":["CUSTOM_SCAN_FINISHED","CUSTOM_SCAN_MALWARE_FOUND","CUSTOM_SCAN_STARTED","USER_SCAN_FINISHED","USER_SCAN_MALWARE_FOUND","USER_SCAN_STARTED"]}},"additionalProperties":false,"required":["rules"]},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/NullAgentResponse.json0000644000000000000000000000652700000000000022561 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"type":"null"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/README.md0000644000000000000000000000040400000000000017521 0ustar # Agent responses validation ## The schemas are stored here Source files are stored in `defence360/src/asyncclient/ui/spa/api` To generate schemas: ``` cd defence360/src/asyncclient/ui/spa/api npm i # one time npm start # on each api/*.ts files change ``` defence360agent/simple_rpc/schema_responses/ReputationAgentResponse.json0000644000000000000000000000777700000000000024011 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ListResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ListResponseData":{"type":"object","properties":{"max_count":{"type":"number"},"items":{"type":"array","items":{"$ref":"#/definitions/ReputationBackendItem"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"ReputationBackendItem":{"type":"object","properties":{"username":{"type":"string"},"domain":{"type":"string"},"threats":{"type":"array","items":{"type":"object","properties":{"type":{"type":["null","string"]},"vendor":{"$ref":"#/definitions/Vendor"},"timestamp":{"type":"number"}},"additionalProperties":false,"required":["timestamp","type","vendor"]}}},"additionalProperties":false,"required":["domain","threats","username"]},"Vendor":{"enum":["google-safe-browsing","mitchellkrogza","openphish","phishtank","spamhaus","yandex-safe-browsing"],"type":"string"},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/TokenAgentResponse.json0000644000000000000000000000653500000000000022726 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/AgentResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"AgentResponseData":{"type":"object","properties":{"items":{"type":"string"},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/WhmcsUpdateResponse.json0000644000000000000000000000252500000000000023106 0ustar { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "result": { "type": "string" }, "messages": {}, "data": { "type": "object", "properties": { "result": { "type": "string" }, "data": { "type": "object", "properties": { "status": { "type": "string" }, "purchase_page_url": { "type": "string", "format": "uri" }, "protection": { "type": "array", "items": { "type": "object", "properties": { "user": { "type": "string" }, "protection": { "type": "string" } }, "required": ["user", "protection"] } } }, "required": ["status", "purchase_page_url", "protection"], "additionalProperties": true }, "strategy": {}, "warnings": {}, "version": {}, "eula": {}, "license": {} }, "required": ["result", "data"], "additionalProperties": true } }, "required": ["result", "data"], "additionalProperties": true }defence360agent/simple_rpc/schema_responses/WordpressDomainsResponse.json0000644000000000000000000000711000000000000024160 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ListResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ListResponseData":{"type":"object","properties":{"max_count":{"type":"number"},"items":{"type":"array","items":{"$ref":"#/definitions/IUserDomain"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"IUserDomain":{"type":"object","properties":{"domain":{"type":"string"},"docroot":{"type":"string"}},"additionalProperties":false,"required":["domain"]},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/schema_responses/WordpressIncidentsListAgentResponse.json0000644000000000000000000001226200000000000026325 0ustar {"type":"object","properties":{"data":{"$ref":"#/definitions/ListResponseData"},"result":{"$ref":"#/definitions/Result"},"messages":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"string"}]},"status":{"enum":["downgrading","failed_to_install","installing","not_installed","running","socket_inaccessible","stopped","upgrading"],"type":"string"}},"additionalProperties":false,"required":["data","messages","result"],"definitions":{"ListResponseData":{"type":"object","properties":{"max_count":{"type":"number"},"items":{"type":"array","items":{"$ref":"#/definitions/IWordpressIncident"}},"version":{"type":"string"},"warnings":{"type":"array","items":{"type":"string"}},"strategy":{"type":"string"},"license":{"anyOf":[{"additionalProperties":false,"type":"object","properties":{"id":{"type":"string"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"message":{"type":["null","string"]},"redirect_url":{"type":["null","string"]},"status":{"type":"boolean"},"user_count":{"type":["null","number"]},"user_limit":{"type":"number"},"ip_license":{"type":"boolean"},"pricing":{"type":"object","properties":{"im_av":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_1":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_30":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_250":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]},"im_360_unlimited":{"type":"object","properties":{"standard":{"type":"string"},"bulk":{"type":"string"}},"additionalProperties":false,"required":["standard"]}},"additionalProperties":false},"expiration":{},"demo":{"type":["null","boolean"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]}},"required":["eligible_for_imunify_patch","id","license_type","message","status","upgrade_url","upgrade_url_360","user_count","user_limit"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean","enum":[false]},"redirect_url":{"type":"null"},"upgrade_url_360":{"type":["null","string"]},"upgrade_url":{"type":["null","string"]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","redirect_url","status","upgrade_url","upgrade_url_360"]},{"additionalProperties":false,"type":"object","properties":{"status":{"type":"boolean"},"license_type":{"anyOf":[{"enum":["imunify360","imunify360Trial","imunifyAV","imunifyAVPlus"],"type":"string"},{"type":"null"}]},"eligible_for_imunify_patch":{"type":["null","boolean"]},"expiration":{}},"required":["eligible_for_imunify_patch","license_type","status"]}]},"eula":{"anyOf":[{"type":"object","properties":{"message":{"type":"string"},"text":{"type":"string"},"updated":{"type":"string"}},"additionalProperties":false,"required":["message","text","updated"]},{"type":"null"}]}},"additionalProperties":false,"required":["items","license","version"]},"IWordpressIncident":{"type":"object","properties":{"abuser":{"type":["null","string"]},"country":{"anyOf":[{"$ref":"#/definitions/Partial"},{"type":"null"}]},"description":{"type":"string"},"id":{"type":"number"},"name":{"type":"string"},"times":{"type":"number"},"rule":{"type":"string"},"is_rule_disabled":{"type":"boolean"},"severity":{"type":["null","number"]},"timestamp":{"type":"number"},"plugin":{"$ref":"#/definitions/RulePlugin"},"domain":{"type":["null","string"]},"extra_info":{"$ref":"#/definitions/IWordpressExtraInfo"}},"additionalProperties":false,"required":["abuser","country","description","domain","id","name","plugin","rule","severity","timestamp"]},"Partial":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"id":{"type":"string"}},"additionalProperties":false},"RulePlugin":{"enum":["cl_dos","control_panel_protector","cphulk","enhanced_dos","lfd","modsec","ossec","unknown","wordpress"],"type":"string"},"IWordpressExtraInfo":{"type":"object","properties":{"cve":{"type":"string"},"mode":{"type":"string"},"target":{"type":"string"},"slug":{"type":"string"},"version":{"type":"string"},"user_logged_in":{"type":["null","string","boolean"]},"username":{"type":"string"},"user_id":{"type":["null","string","number"]},"site_path":{"type":"string"},"request_method":{"type":"string"},"script_filename":{"type":"string"},"php_self":{"type":"string"},"path_info":{"type":"string"},"request_uri":{"type":"string"},"query_string":{"type":"string"},"http_x_forwarded_for":{"type":"string"},"http_user_agent":{"type":"string"},"http_referer":{"type":"string"},"files":{"type":"string"},"get_names":{"type":"string"},"post_names":{"type":"string"},"raw_data":{"type":"string"}},"additionalProperties":false},"Result":{"enum":["error","success","warnings"],"type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}defence360agent/simple_rpc/wordpress_security_plugin.py0000644000000000000000000001600300000000000020612 0ustar import logging import os import pwd from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import ( CommonEndpoints, RootEndpoints, bind, ) from defence360agent.utils import Scope, is_root_user from defence360agent.contracts.messages import MessageType from defence360agent.model.wordpress_incident import get_wordpress_incidents from defence360agent.model.wp_disabled_rule import ( enrich_incidents_with_disabled_state, ) from defence360agent.wordpress.site_repository import ( get_installed_sites_paginated, ) from defence360agent.wordpress.utils import get_domain_paths logger = logging.getLogger(__name__) def get_user_id_and_site_for_query( user: str | None = None, site_search: str | None = None ) -> tuple[int | None, str | None]: """ Determine the user_id and site_path for filtering WordPress incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents (user/site_search ignored) 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by site_search: Site path to filter by Returns: Tuple of (user_id, site_path) to filter by, or (None, None) for all Raises: KeyError: If the specified user doesn't exist ValueError: If proxy service call is missing required parameters """ current_uid = os.getuid() if is_root_user(): # Root user can see all incidents or filter by user logger.debug("Root user querying incidents, user filter: %s", user) user_id = None # Root can see all incidents by default if user is not None: # Root user specified a username to filter by try: user_id = pwd.getpwnam(user).pw_uid logger.debug( "Filtering incidents for user %s (uid=%d)", user, user_id ) except KeyError: logger.warning("User not found: %s", user) raise KeyError(f"User '{user}' not found") return user_id, site_search return current_uid, site_search class WordpressEndpoints(RootEndpoints): SCOPE = Scope.AV_IM360 @bind("wordpress-plugin", "install-on-new-sites") async def wordpress_plugin_install(self): await self._sink.process_message( MessageType.WordpressPluginAction(action="install_on_new_sites") ) @bind("wordpress-plugin", "tidy-up") async def wordpress_plugin_tidy_up(self): await self._sink.process_message( MessageType.WordpressPluginAction(action="tidy_up") ) @bind("wordpress-plugin", "update") async def wordpress_plugin_update(self): await self._sink.process_message( MessageType.WordpressPluginAction(action="update_existing") ) @bind("wordpress-plugin", "install-and-update") async def wordpress_plugin_install_and_update(self): await self._sink.process_message( MessageType.WordpressPluginAction(action="install_and_update") ) class WordpressCommonEndpoints(CommonEndpoints): SCOPE = Scope.AV_IM360 @bind("wordpress-plugin", "list-incidents") async def wordpress_plugin_list_incidents( self, user: str | None = None, site_search: str | None = None, limit: int = 50, offset: int = 0, by_abuser_ip: str | None = None, by_country_code: str | None = None, by_domain: str | None = None, search: str | None = None, since: int | None = None, to: int | None = None, order_by: list | None = None, include_hidden: bool = False, ) -> list[dict]: """ List WordPress security incidents. Three calling contexts: 1. Root user: Can query all incidents or filter by specific user 2. Non-root user: Can only query their own incidents 3. Proxy service: Both user and site_search must be set, restricted to that site Args: user: Username to filter by (root or proxy service) site_search: Site path to filter by (proxy service only) limit: Maximum number of incidents to return offset: Number of incidents to skip by_abuser_ip: Filter by attacker IP address by_country_code: Filter by country code by_domain: Filter by domain search: Search across multiple fields since: Filter by timestamp >= this value (unix timestamp) to: Filter by timestamp <= this value (unix timestamp) order_by: List of fields to order by (e.g., ['timestamp-', 'severity-']) Returns: List of incident dictionaries Raises: ValidationError: If the specified user doesn't exist """ try: user_id, site_path = get_user_id_and_site_for_query( user, site_search ) except KeyError as e: raise ValidationError(str(e)) from e incidents = get_wordpress_incidents( limit=limit, offset=offset, user_id=user_id, by_abuser_ip=by_abuser_ip, by_country_code=by_country_code, by_domain=by_domain, search=search, site_search=site_path, since=since, to=to, order_by=order_by, include_hidden=include_hidden, ) # Fields transformation for UI for incident in incidents: incident["times"] = incident.pop("retries") country = incident.pop("country") incident["country"] = ( {"code": country} if country is not None else None ) enrich_incidents_with_disabled_state(incidents) return incidents @bind("wordpress-plugin", "list-sites") async def list_sites(self, limit=50, offset=0, user=None): """ List WordPress sites with Imunify plugin installed. For root users: returns all sites. For non-root users: returns only sites belonging to that user. """ uid = None if user: try: uid = pwd.getpwnam(user).pw_uid except KeyError: return 0, [] max_count, sites = get_installed_sites_paginated( uid=uid, limit=limit, offset=offset ) # Get docroot to domain mapping from control panel docroot_domains = await get_domain_paths() # Build result with primary domain resolution items = [] for site in sites: # Get domains from control panel, fall back to stored domain domains = docroot_domains.get(site.docroot, []) primary_domain = domains[0] if domains else site.domain items.append( { "domain": primary_domain, "docroot": site.docroot, } ) return max_count, items defence360agent/simple_rpc/wp_disabled_rules.py0000644000000000000000000002437200000000000016754 0ustar """RPC endpoints for WordPress disabled protection rules.""" import asyncio import logging import pwd import time from defence360agent.contracts.messages import MessageType from defence360agent.contracts.permissions import ( WP_WAF_RULES_EDIT, check_permission, ) from defence360agent.contracts.plugins import MessageSink from defence360agent.files import Index, WP_RULES from defence360agent.model.wp_disabled_rule import WPDisabledRule from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import CommonEndpoints, bind from defence360agent.subsys.panels import hosting_panel from defence360agent.utils import Scope, log_future_errors from defence360agent.wordpress.changelog_processor import ( ChangelogProcessor, ) from defence360agent.wordpress.plugin import ( redeploy_rules_php, update_disabled_rules_on_sites, ) from defence360agent.wordpress.site_repository import ( get_installed_sites_by_domains, ) from defence360agent.wordpress.wp_rules import get_wp_rules_data logger = logging.getLogger(__name__) async def _get_user_domains(user: str) -> list[str]: """ Get domains for a user from the hosting panel. Returns: List of domains the user owns, or empty list on error. """ try: hp = hosting_panel.HostingPanel() domains_per_user = await hp.get_domains_per_user() return domains_per_user.get(user, []) except Exception as e: logger.warning("Failed to get domains for user %s: %s", user, e) return [] async def _validate_user_domains( user: str, domains: list[str] | None ) -> list[str]: """ Validate and filter domains for a non-root user. If no domains specified, returns all user's domains. If domains specified, filters to only those the user owns. Args: user: Username to validate domains for domains: Requested domains, or None for all user's domains Returns: List of validated domains the user can access Raises: ValidationError: If user has no domains or no access to requested domains """ user_domains = await _get_user_domains(user) if not domains: if not user_domains: raise ValidationError("No domains found for user") return user_domains authorized_domains = [d for d in domains if d in user_domains] if not authorized_domains: raise ValidationError( "You don't have access to any of the specified domains" ) return authorized_domains def _enrich_with_metadata( disabled_rules: list[dict], wp_rules_data: dict | None ) -> list[dict]: """ Enrich disabled rules with metadata from wp-rules.yaml. Args: disabled_rules: List of disabled rule dicts from WPDisabledRule.fetch() wp_rules_data: Parsed wp-rules.yaml data, or None if unavailable Returns: List of enriched rule dicts with component and versions added """ enriched = [] for rule in disabled_rules: rule_id = rule["rule_id"] metadata = wp_rules_data.get(rule_id, {}) if wp_rules_data else {} enriched.append( { **rule, "component": metadata.get("target"), "versions": metadata.get("versions"), } ) return enriched async def _jit_sync_changelogs( domains: list[str], sink: MessageSink | None = None ) -> None: """Process pending changelog files for the given domains before an API change. This "Just-in-Time" sync ensures the database reflects any WordPress-side changes before the agent applies its own disable/enable operation. File regeneration (disabled-rules.php) is intentionally skipped here because the calling API endpoint will regenerate files after its own DB mutation. """ try: sites = get_installed_sites_by_domains(domains) if not sites: return await ChangelogProcessor().process_changelogs_for_sites( sites, sink=sink ) except Exception as e: logger.warning("JIT changelog sync failed: %s", e, exc_info=True) class WPDisabledRulesEndpoints(CommonEndpoints): """Endpoints for listing disabled WordPress protection rules.""" SCOPE = Scope.AV_IM360 @bind("wordpress-plugin", "rules", "list-disabled") async def list_disabled_rules( self, limit: int = 50, offset: int = 0, domains: list[str] | None = None, user: str | None = None, ) -> tuple[int, list[dict]]: """ List disabled WordPress protection rules with metadata. When user is provided, returns rules for that user's domains. Otherwise, returns all disabled rules. Args: limit: Maximum number of rules to return offset: Number of rules to skip domains: Filter by specific domains (optional) user: Username (populated by middleware) Returns: Tuple of (total_count, list of enriched rule dicts) """ if user: user_domains = await _get_user_domains(user) if not domains: domains = user_domains else: domains = [d for d in domains if d in user_domains] # if user cannot access any of the requested domains, return empty list if not domains: return 0, [] # Fetch disabled rules from database # Root users see all rules (including global), non-root only see their domain rules total_count, disabled_rules = WPDisabledRule.fetch( limit=limit, offset=offset, user_domains=domains, include_global=user is None, ) # Load wp-rules metadata for enrichment try: wp_rules_index = Index(WP_RULES, integrity_check=False) wp_rules_data = get_wp_rules_data(wp_rules_index) except Exception as e: logger.warning("Failed to load wp-rules data: %s", e) wp_rules_data = None # Enrich with metadata enriched_rules = _enrich_with_metadata(disabled_rules, wp_rules_data) return total_count, enriched_rules async def _toggle_rule( self, action: str, rule: str, domains: list[str] | None, user: str | None, ) -> dict: """Shared implementation for disable/enable rule endpoints.""" await check_permission(WP_WAF_RULES_EDIT, user) if user is None: user_id = 0 else: try: user_id = pwd.getpwnam(user).pw_uid except KeyError: raise ValidationError(f"User '{user}' not found") if user: domains = await _validate_user_domains(user, domains) # JIT Sync: process pending changelogs before applying API changes. # Skipped for global operations (domains=None) because global and # domain-level disables are independent scopes and cannot conflict. if domains: await _jit_sync_changelogs(domains, self._sink) if action == "disable": WPDisabledRule.store( rule_id=rule, domains=domains, source=WPDisabledRule.SOURCE_AGENT, user_id=user_id, ) message_cls = MessageType.WPRuleDisabled else: WPDisabledRule.remove(rule_id=rule, domains=domains) message_cls = MessageType.WPRuleEnabled try: await self._sink.process_message( message_cls( plugin_id="wordpress", rule=rule, domains=domains or [], timestamp=time.time(), user_id=user_id, source=WPDisabledRule.SOURCE_AGENT, ) ) except Exception as e: logger.error( "Failed to report rule %s for %s: %s", action, rule, e ) if domains: # Domain-specific: update disabled-rules.php for affected sites task = asyncio.create_task( update_disabled_rules_on_sites(domains=domains) ) else: # Global: re-deploy rules.php with the rule filtered out task = asyncio.create_task(redeploy_rules_php()) task.add_done_callback(log_future_errors) return {} @bind("wordpress-plugin", "rules", "disable") async def disable_rule( self, rule: str, domains: list[str] | None = None, user: str | None = None, ) -> dict: """ Disable a WordPress protection rule globally or for specific domains. Root users can disable globally (no domains) or for specific domains. Non-root users can disable for all their domains (by specifying no domains) or for specific domains. Non-root users can only disable for domains they own. Args: rule: The rule ID to disable (e.g., "CVE-2025-001") domains: List of domains to disable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success. """ return await self._toggle_rule("disable", rule, domains, user) @bind("wordpress-plugin", "rules", "enable") async def enable_rule( self, rule: str, domains: list[str] | None = None, user: str | None = None, ) -> dict: """ Re-enable a WordPress protection rule globally or for specific domains. Root users can enable globally (no domains) or for specific domains. Non-root users can enable for all their domains (no domains) or specific ones. Non-root users can only enable for domains they own. Note: Enabling at one scope doesn't affect the other scope. E.g., enabling globally leaves domain-specific disables intact. Args: rule: The rule ID to enable (e.g., "CVE-2025-001") domains: List of domains to enable the rule for, or None for global user: Username (populated by middleware for non-root users) Returns: Empty dict on success """ return await self._toggle_rule("enable", rule, domains, user) defence360agent/simple_rpc/wp_waf_bulk.py0000644000000000000000000001757200000000000015571 0ustar """Bulk WAF set + status endpoints.""" import asyncio import logging import pwd from defence360agent.contracts.config import Wordpress from defence360agent.rpc_tools import ValidationError from defence360agent.rpc_tools.lookup import RootEndpoints, bind from defence360agent.subsys.panels import hosting_panel from defence360agent.utils import Scope from defence360agent.utils.config import update_config from defence360agent.wordpress.plugin import ( waf_global_snapshot, waf_status_and_source_for_user_sync, ) from defence360agent.wordpress.site_repository import ( count_installed_sites_by_uid, ) logger = logging.getLogger(__name__) _MAX_CONCURRENT = 10 _STATUS_ENABLED = "enabled" _STATUS_DISABLED = "disabled" # Upper bound on items returned in a single response, regardless of --limit, # so enumerating a server with tens of thousands of accounts can't build an # unbounded payload. _SAFETY_CAP = 500 def _resolve_accounts_sync( users: list[str], ) -> list[tuple[str, int | None, bool, str]]: rows = [] for name in users: try: uid = pwd.getpwnam(name).pw_uid except KeyError: uid = None enabled, source = waf_status_and_source_for_user_sync(name) rows.append((name, uid, enabled, source)) return rows def _status_item( row: tuple[str, int | None, bool, str], site_counts: dict[int, int] ) -> dict: name, uid, enabled, source = row return { "name": name, "waf_status": _STATUS_ENABLED if enabled else _STATUS_DISABLED, "source": source, "wp_sites": site_counts.get(uid, 0), } def _matches( row: tuple[str, int | None, bool, str], status: str | None, source: str | None, ) -> bool: _, _, enabled, src = row waf_status = _STATUS_ENABLED if enabled else _STATUS_DISABLED if status is not None and waf_status != status: return False if source is not None and src != source: return False return True class WordpressWafBulkEndpoints(RootEndpoints): SCOPE = Scope.AV_IM360 @bind("wordpress-plugin", "waf", "set") async def waf_set( self, status: str, all_users: bool = False, users: list[str] | None = None, ) -> dict: if all_users and users is not None: raise ValidationError( "Specify either --all-users or --users, not both" ) if not all_users and users is None: raise ValidationError("Specify either --all-users or --users") if users is not None and not users: raise ValidationError("--users must not be empty") if not Wordpress.SECURITY_PLUGIN_ENABLED: raise ValidationError( "WordPress Security Plugin is disabled." " Enable it before changing WAF settings." ) logger.warning( "AUDIT wordpress-plugin.waf.set status=%r all_users=%r users=%r", status, all_users, users, ) try: panel_users = set(await hosting_panel.HostingPanel().get_users()) except Exception as e: raise ValidationError( f"Could not enumerate hosting users: {e}" ) from e succeeded: list[str] = [] skipped: list[dict] = [] failed: list[dict] = [] if all_users: valid_users = list(panel_users) else: valid_users = [] for u in dict.fromkeys(users): if u in panel_users: valid_users.append(u) else: skipped.append({"user": u, "reason": "Not a hosting user"}) waf_value = status == "enabled" async def _apply_to_user(u: str) -> tuple[str, str | None]: try: await update_config( self._sink, {"WORDPRESS": {"waf_enabled": waf_value}}, user=u, ) return u, None except Exception as e: return u, str(e) for i in range(0, len(valid_users), _MAX_CONCURRENT): batch = [ _apply_to_user(u) for u in valid_users[i : i + _MAX_CONCURRENT] ] results = await asyncio.gather(*batch) for u, err in results: if err is None: succeeded.append(u) else: failed.append({"user": u, "reason": err}) items = [ *[ {"user": u, "status": "succeeded", "reason": ""} for u in succeeded ], *[ {"user": s["user"], "status": "skipped", "reason": s["reason"]} for s in skipped ], *[ {"user": f["user"], "status": "failed", "reason": f["reason"]} for f in failed ], ] return { "items": items, "succeeded": succeeded, "skipped": skipped, "failed": failed, } @bind("wordpress-plugin", "waf", "status") async def waf_status( self, user: str | None = None, status: str | None = None, source: str | None = None, limit: int | None = None, offset: int = 0, ) -> dict: if limit is not None and limit < 0: raise ValidationError("--limit must be >= 0") if offset < 0: raise ValidationError("--offset must be >= 0") loop = asyncio.get_running_loop() ( security_plugin_enabled, global_waf_enabled, global_waf_default, ) = waf_global_snapshot() try: panel_users = list( dict.fromkeys(await hosting_panel.HostingPanel().get_users()) ) except Exception as e: raise ValidationError( f"Could not enumerate hosting users: {e}" ) from e if user is not None: if user not in set(panel_users): raise ValidationError(f"{user} is not a hosting user") panel_users = [user] site_counts = await loop.run_in_executor( None, count_installed_sites_by_uid ) page_size = _SAFETY_CAP if limit is None else min(limit, _SAFETY_CAP) if status is None and source is None: # No status/source filter: the total is just the account count and # results are ordered by name (known before resolution), so resolve # only the requested page instead of every account — otherwise a # small --limit/--offset page still costs O(all-users) work. total_count = len(panel_users) page = sorted(panel_users)[offset : offset + page_size] rows = await loop.run_in_executor( None, _resolve_accounts_sync, page ) items = [_status_item(row, site_counts) for row in rows] else: # A status/source filter's total is post-filter, so every account # must be resolved before it can be counted and paginated. rows = await loop.run_in_executor( None, _resolve_accounts_sync, panel_users ) items = [ _status_item(row, site_counts) for row in rows if _matches(row, status, source) ] items.sort(key=lambda i: i["name"]) total_count = len(items) items = items[offset : offset + page_size] return { "security_plugin_enabled": security_plugin_enabled, "global_waf": ( _STATUS_ENABLED if global_waf_enabled else _STATUS_DISABLED ), "global_waf_default": ( _STATUS_ENABLED if global_waf_default else _STATUS_DISABLED ), "total_count": total_count, "items": items, } defence360agent/subsys/0000755000000000000000000000000000000000000012076 5ustar defence360agent/subsys/__init__.py0000644000000000000000000000000000000000000014175 0ustar defence360agent/subsys/__pycache__/0000755000000000000000000000000000000000000014306 5ustar defence360agent/subsys/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000030100000000000021500 0ustar r_jdS)NrT/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/__init__.pyrsrdefence360agent/subsys/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000030100000000000020541 0ustar r_jdS)NrT/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/__init__.pyrsrdefence360agent/subsys/__pycache__/ainotify.cpython-311.opt-1.pyc0000644000000000000000000002752600000000000021605 0ustar r_jLddlmZddlZddlZddlZddlZddlZddlZddlZddl m Z eddZ ej e ZGddZGdd ZdS) ) namedtupleN)sysctlEvent)pathflagscookienamewdceZdZdZdZdZdZdZdZdZ dZ d Z d Z d Z d Zd ZdZdZdZdZdZdZdZdZdZdejdkrdndZejedZe j!dZ"e#dZ$e#dZ%e#d Z&e#d!Z'e#d"Z(e#d#Z)d$S)%InotifyzE Tiny wrapper for inotify api. See `man inotify` for details  @iii i@iiiii i@lzlibc.{}Darwinzso.6dylibT) use_errnoiIIIcttj||}|dkr5tj}t |t j||S)a Wrapper to all calls to C functions. Raises OSError with appropriate errno as argument in case of error return value. :param method: method to call :param args: method args :return: called function return value in case of success )getattrr _libcctypes get_errnoOSErrorosstrerror)methodargsreterrnos T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/ainotify.py_callz Inotify._call4sR-ggmV,,d3 "99$&&E%U!3!344 4 c6tdS)z Initialize an inotify instance. See `man inotify_init` for details :return: a file descriptor of new inotify instance inotify_initr r)r*r(initz Inotify.initCs}}^,,,r*c<td|||S)a Add a watch to an initialized inotify instance. This method is idempotent. If called twice with the same :fd: and :path: and different mask, will change watch flags of current watch. See `man inotify_add_watch` for details :param fd: file descriptor returned by `init()` :param path: path to file or directory to watch :param mask: bitmask of events to monitor :return: file descriptor of watch inotify_add_watchr-)fdrmasks r( add_watchzInotify.add_watchLs}}0"dDAAAr*c:td||S)z Remove existing watch from inotify instance. :param fd: file descriptor of inotify instance :param wd: watch file descriptor, returned by `add_watch()` :return: zero inotify_rm_watchr-)r2r s r(rm_watchzInotify.rm_watchZs}}/R888r*c@tj|S)z Unpacks prefix of event struct. See `man inotify` for details :param data: struct bytestring :return: tuple of (wd, flag, cookie, length) )r event_prefixunpackdatas r( unpack_prefixzInotify.unpack_prefixds#**4000r*c~tjdt|z|ddS)z Unpack name field of inotify event struct See `man inotify` for details :param data: struct bytestring :return: name string z%dsr)structr:lenrstripr;s r( unpack_namezInotify.unpack_namens4}USYY.55a8??HHHr*N)*__name__ __module__ __qualname____doc__ACCESSMODIFYATTRIB CLOSE_WRITE CLOSE_NOWRITEOPEN MOVED_FROMMOVED_TOCREATEDELETE DELETE_SELF MOVE_SELFUNMOUNT Q_OVERFLOWIGNOREDONLYDIR DONT_FOLLOW EXCL_UNLINKMASK_ADDISDIRONESHOTformatplatformsystem_nrCDLLrr@Structr9 staticmethodr)r/r4r7r=rCr.r*r(r r sF F FKM DJH F FKIGJGGKKH EG   OHO$5$5$A$A&&w O OB FKd + + +E 6=((L  \ --\- B B\ B99\911\1II\IIIr*r cZeZdZdZdZdZdZdZddZdZ d Z d Z d Z d Z d ZdZdZdS)Watcherz1 Asynchronous watcher for inotify events rg?zfs.inotify.max_user_watchesNc||_t|_t j|_|p |jj|_|j |j|j | dSN) _loopr r/_fdasyncioQueue_queueput _callback add_reader_read _reset_state)selfloop coro_callbacks r(__init__zWatcher.__init__si <<>>moo &9$+/ dh 333 r*c0i|_i|_d|_dS)Nr*)paths descriptorsbufrss r(rrzWatcher._reset_states r*cF|xjtj|j|jz c_t jj}t|j|krIt |jd|\}}}}||z}t |j||}|j|d|_||j vr|j |}|t j zr1td||||t jzrtdt%|||||} |j|| t|j|kGdSdS)Nz(Got IGNORED event for %s, cleaning watchzInotify queue overflow)rzr"readrj _CHUNK_SIZEr r9sizerAr=rCrxrVloggerwarning_cleanup_watchrUerrorrri create_taskro) rs struct_sizer rrlength struct_endr revs r(rqz Watcher._reads BGDHd&6777*/ $(mm{**(/(=(=+&)) %Bvv%v-J&&tx J0F'GHHDx ,DH##:b>Dw& >##D)))w))  5666tUFD"55B J " "4>>"#5#5 6 6 6/$(mm{******r*ctj|j}|t||jzz}t d|j|tj|j|dS)NzRaising %s to %s)rr}_MAX_USER_WATCHESint_WATCHERS_RAISE_COEFFrinfowrite)rscurrent_max_watchesnew_max_watcherss r(_raise_user_watcheszWatcher._raise_user_watchessx$k$*@AA. $"< <2 2     68H     T+-=>>>>>r*c|j|j tj|j|d|_dS#|d|_wxYw)za Close watcher. Close inotify fd, remove reader and reset state :return: N)ri remove_readerrjr"closerrr{s r(rz Watcher.closest   ***  HTX         DHHH      DHOOOOs AA4ct|ts Jdtd|d} t|j||}||j|<||j|<dS#t$rz}||j krN|j tj kr9| |dz }td|Yd}~td|d}~wwxYw) z Add file to watch :param path: file or directory to watch :param mask: events mask for this watch zPath must be bytesz Watching %rrTr z-Inotify: not enough watches (%r), retrying...Nz Inotify failed while watching %r) isinstancebytesrrr r4rjrxryr!_MAX_WATCH_RETRIESr'ENOSPCrrr)rsrr3retriesr es r(watchz Watcher.watchs $&&<<(<<<& M4(((  &&txt<<!% 2)+ &   d5555<//,,...qLGNNGHHHH ?FFF s5A66 C:AC5C55C:cz|j|d}||j|ddSdSrh)rypoprx)rsr descriptors r(rzWatcher._cleanup_watchsD%))$55  ! JNN:t , , , , , " !r*c||jvrdStd| t|j|j|||dS#||wxYw)zq Remove file or directory from watch :param path: file or directory to remove watch from NzStop watching %r)ryrrr r7rjr)rsrs r(unwatchzWatcher.unwatchs t' ' ' F &--- &   TXt'7'= > > >    % % % % %D   % % % %s +A**Bc~K|jd{V}td||S)zF Get watch event :return: `Event` named tuple NzInotify event: %s)rmgetrdebug)rsevents r( get_eventzWatcher.get_eventsE koo'''''''' (%000 r*rh)rDrErFrGr~rrrrvrrrqrrrrrrr.r*r(rereysK5 777:???   :--- & & &r*re) collectionsrrkrr'loggingr"r@r^defence360agent.subsysrr getLoggerrDrr rer.r*r(rs""""""  )))))) 7EFF  8 $ $dIdIdIdIdIdIdIdIN@@@@@@@@@@r*defence360agent/subsys/__pycache__/ainotify.cpython-311.pyc0000644000000000000000000002752600000000000020646 0ustar r_jLddlmZddlZddlZddlZddlZddlZddlZddlZddl m Z eddZ ej e ZGddZGdd ZdS) ) namedtupleN)sysctlEvent)pathflagscookienamewdceZdZdZdZdZdZdZdZdZ dZ d Z d Z d Z d Zd ZdZdZdZdZdZdZdZdZdZdejdkrdndZejedZe j!dZ"e#dZ$e#dZ%e#d Z&e#d!Z'e#d"Z(e#d#Z)d$S)%InotifyzE Tiny wrapper for inotify api. See `man inotify` for details  @iii i@iiiii i@lzlibc.{}Darwinzso.6dylibT) use_errnoiIIIcttj||}|dkr5tj}t |t j||S)a Wrapper to all calls to C functions. Raises OSError with appropriate errno as argument in case of error return value. :param method: method to call :param args: method args :return: called function return value in case of success )getattrr _libcctypes get_errnoOSErrorosstrerror)methodargsreterrnos T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/ainotify.py_callz Inotify._call4sR-ggmV,,d3 "99$&&E%U!3!344 4 c6tdS)z Initialize an inotify instance. See `man inotify_init` for details :return: a file descriptor of new inotify instance inotify_initr r)r*r(initz Inotify.initCs}}^,,,r*c<td|||S)a Add a watch to an initialized inotify instance. This method is idempotent. If called twice with the same :fd: and :path: and different mask, will change watch flags of current watch. See `man inotify_add_watch` for details :param fd: file descriptor returned by `init()` :param path: path to file or directory to watch :param mask: bitmask of events to monitor :return: file descriptor of watch inotify_add_watchr-)fdrmasks r( add_watchzInotify.add_watchLs}}0"dDAAAr*c:td||S)z Remove existing watch from inotify instance. :param fd: file descriptor of inotify instance :param wd: watch file descriptor, returned by `add_watch()` :return: zero inotify_rm_watchr-)r2r s r(rm_watchzInotify.rm_watchZs}}/R888r*c@tj|S)z Unpacks prefix of event struct. See `man inotify` for details :param data: struct bytestring :return: tuple of (wd, flag, cookie, length) )r event_prefixunpackdatas r( unpack_prefixzInotify.unpack_prefixds#**4000r*c~tjdt|z|ddS)z Unpack name field of inotify event struct See `man inotify` for details :param data: struct bytestring :return: name string z%dsr)structr:lenrstripr;s r( unpack_namezInotify.unpack_namens4}USYY.55a8??HHHr*N)*__name__ __module__ __qualname____doc__ACCESSMODIFYATTRIB CLOSE_WRITE CLOSE_NOWRITEOPEN MOVED_FROMMOVED_TOCREATEDELETE DELETE_SELF MOVE_SELFUNMOUNT Q_OVERFLOWIGNOREDONLYDIR DONT_FOLLOW EXCL_UNLINKMASK_ADDISDIRONESHOTformatplatformsystem_nrCDLLrr@Structr9 staticmethodr)r/r4r7r=rCr.r*r(r r sF F FKM DJH F FKIGJGGKKH EG   OHO$5$5$A$A&&w O OB FKd + + +E 6=((L  \ --\- B B\ B99\911\1II\IIIr*r cZeZdZdZdZdZdZdZddZdZ d Z d Z d Z d Z d ZdZdZdS)Watcherz1 Asynchronous watcher for inotify events rg?zfs.inotify.max_user_watchesNc||_t|_t j|_|p |jj|_|j |j|j | dSN) _loopr r/_fdasyncioQueue_queueput _callback add_reader_read _reset_state)selfloop coro_callbacks r(__init__zWatcher.__init__si <<>>moo &9$+/ dh 333 r*c0i|_i|_d|_dS)Nr*)paths descriptorsbufrss r(rrzWatcher._reset_states r*cF|xjtj|j|jz c_t jj}t|j|krIt |jd|\}}}}||z}t |j||}|j|d|_||j vr|j |}|t j zr1td||||t jzrtdt%|||||} |j|| t|j|kGdSdS)Nz(Got IGNORED event for %s, cleaning watchzInotify queue overflow)rzr"readrj _CHUNK_SIZEr r9sizerAr=rCrxrVloggerwarning_cleanup_watchrUerrorrri create_taskro) rs struct_sizer rrlength struct_endr revs r(rqz Watcher._reads BGDHd&6777*/ $(mm{**(/(=(=+&)) %Bvv%v-J&&tx J0F'GHHDx ,DH##:b>Dw& >##D)))w))  5666tUFD"55B J " "4>>"#5#5 6 6 6/$(mm{******r*ctj|j}|t||jzz}t d|j|tj|j|dS)NzRaising %s to %s)rr}_MAX_USER_WATCHESint_WATCHERS_RAISE_COEFFrinfowrite)rscurrent_max_watchesnew_max_watcherss r(_raise_user_watcheszWatcher._raise_user_watchessx$k$*@AA. $"< <2 2     68H     T+-=>>>>>r*c|j|j tj|j|d|_dS#|d|_wxYw)za Close watcher. Close inotify fd, remove reader and reset state :return: N)ri remove_readerrjr"closerrr{s r(rz Watcher.closest   ***  HTX         DHHH      DHOOOOs AA4ct|ts Jdtd|d} t|j||}||j|<||j|<dS#t$rz}||j krN|j tj kr9| |dz }td|Yd}~td|d}~wwxYw) z Add file to watch :param path: file or directory to watch :param mask: events mask for this watch zPath must be bytesz Watching %rrTr z-Inotify: not enough watches (%r), retrying...Nz Inotify failed while watching %r) isinstancebytesrrr r4rjrxryr!_MAX_WATCH_RETRIESr'ENOSPCrrr)rsrr3retriesr es r(watchz Watcher.watchs $&&<<(<<<& M4(((  &&txt<<!% 2)+ &   d5555<//,,...qLGNNGHHHH ?FFF s5A66 C:AC5C55C:cz|j|d}||j|ddSdSrh)rypoprx)rsr descriptors r(rzWatcher._cleanup_watchsD%))$55  ! JNN:t , , , , , " !r*c||jvrdStd| t|j|j|||dS#||wxYw)zq Remove file or directory from watch :param path: file or directory to remove watch from NzStop watching %r)ryrrr r7rjr)rsrs r(unwatchzWatcher.unwatchs t' ' ' F &--- &   TXt'7'= > > >    % % % % %D   % % % %s +A**Bc~K|jd{V}td||S)zF Get watch event :return: `Event` named tuple NzInotify event: %s)rmgetrdebug)rsevents r( get_eventzWatcher.get_eventsE koo'''''''' (%000 r*rh)rDrErFrGr~rrrrvrrrqrrrrrrr.r*r(rereysK5 777:???   :--- & & &r*re) collectionsrrkrr'loggingr"r@r^defence360agent.subsysrr getLoggerrDrr rer.r*r(rs""""""  )))))) 7EFF  8 $ $dIdIdIdIdIdIdIdIN@@@@@@@@@@r*defence360agent/subsys/__pycache__/backup_systems.cpython-311.opt-1.pyc0000644000000000000000000006204000000000000023005 0ustar r_j,ddlZddlZddlZddlmZddlmZmZmZm Z ddl m Z m Z m ZmZmZmZmZmZmZmZmZmZmZmZddlmZddlmZmZddlm Z ddl!m"Z"dd l#m$Z$e sdd l%m&Z&dd l'm(Z(dd l)m*Z*m+Z+ej,e-Z.d Z/dee0fdZ1 d.dee0effdZ2de e0fdZ3de e4fdZ5dZ6Gdde7Z8GddZ9Gdde9Z:Gdde9Z;Gdde9Z<Gd d!e9Z=Gd"d#e9Z>Gd$d%e9Z?Gd&d'e9Z@Gd(d)e@ZAGd*d+eAZBGd,d-eAZCdS)/N)timezone)CallableDictListOptional)ACRONISANTIVIRUS_MODE AcronisBackup BackupConfig BackupRestore CLOUDLINUXCLOUDLINUX_ON_PREMISE CLUSTERLOGICSCPANELCore DIRECTADMINPLESKR1SOFTSAMPLE_BACKEND) LicenseCLN)BackupNotFoundRestCLN)cPanel) DirectAdmin)Plesk)backup_backends) BackupFailed)BackendNonApplicableErrorBackendNotAuthorizedErrorc td|S#ttf$r#td|wxYw)NT)include_samplez"Backup system is not available: {})_get_avalible_backendsKeyErrorr ValueErrorformat)names Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/backup_systems.py get_backendr((siL@%T:::4@BBB / 0LLL=DDTJJKKKLs  4Areturncg}tdD]5\}} |||&#t$rY2wxYw|S)NF) include_cl)r"itemsappendr)namesr&clss r'get_available_backends_namesr0/s E+u===CCEE c  CEEE LL    )    D  Ls A  AAFTctttti}tjr|rt |t<tjrt|t<tj rt|t<nEtj rt |t"<n"t%j rt&|t(<|rt*|t,<|SN)rAcronisrR1Softr CL_BACKUP_ALLOWED CloudLinuxr CL_ON_PREMISE_BACKUP_ALLOWEDCloudLinuxOnPremiserr is_installed cPanelBackuprr PleskBackuprrDirectAdminBackuprSampler)r!r+backendss r'r"r"=s H &*:*)1>*=&' 2'    2%  ! # #2 1*#) Octdi}|do|dSN BACKUP_SYSTEMenabled backup_system)r config_to_dictget)confs r'get_current_backendrHWsJ >> ( ( * * . . C CD 88I   <488O#<#<.wrapperfs  3q.t...v........BB  & &r & 2 2 2 2C & &r & 2 2 2 2 s 2A )rVrWs` r' transactionalrYes# Nr?ceZdZdS)BackupExceptionN)__name__ __module__ __qualname__rXr?r'r[r[rsDr?r[c`eZdZd dZdZdZddZdZdZd Z d e fd Z d e e fd ZdS) BackupSystemNc"||_||_dSr2)r&log_path)selfr&rbs r'__init__zBackupSystem.__init__ws   r?cnd||r|jnddi}t|dddS)NrB)rCrDT) overwritevalidate)r&r dict_to_config)rcrCnew_confs r'rQz#BackupSystem._update_backups_config{sN ".5!?4  %%h$%NNNNNr?c6K|ddS)NTrOrP)rcrRrSs r'initzBackupSystem.inits# ##D#11111r?Fc6K|ddS)NFrOrP)rcdelete_backupss r'disablezBackupSystem.disables# ##E#22222r?c KiSr2rXrcs r'checkzBackupSystem.check  r?c KiSr2rXrps r'showzBackupSystem.showrrr?c KdSr2rXrps r' make_backupzBackupSystem.make_backups  r?r)cKtdi}|do|d|jkSrA)r rErFr&)rcrGs r' check_statezBackupSystem.check_statesU~~,,..22?BGGxx ""Mtxx'@'@DI'MMr?c KdSr2rXrps r'rJz&BackupSystem.get_last_backup_timestamps tr?r2F)r\r]r^rdrQrkrnrqrtrvboolrxrintrJrXr?r'r`r`vs!!!!OOO2223333   N4NNNN#r?r`ceZdZfdZxZS)r;cTttdSr2)superrdrrc __class__s r'rdzPleskBackup.__init__s! r?r\r]r^rd __classcell__rs@r'r;r;s8         r?r;ceZdZfdZxZS)r:cTttdSr2)rrdrrs r'rdzcPanelBackup.__init__s!      r?rrs@r'r:r:s8!!!!!!!!!r?r:ceZdZfdZxZS)r<cTttdSr2)rrdrrs r'rdzDirectAdminBackup.__init__s! %%%%%r?rrs@r'r<r<s8&&&&&&&&&r?r<c@eZdZfdZdefdZedZxZS)r4ctttjdd|_dS)Nr1softTasync_)rrdrrrKrs r'rdzR1Soft.__init__s6    &.xEEE r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))username timestampiprX.0kvs r' zR1Soft.show..s4   1333 q333r?rKinfor,rc info_datas r'rtz R1Soft.showV,++--------   !))    r?cPK|j||||d{VdSr2rKrk)rcrrpasswordencryption_keyrSs r'rkz R1Soft.inits:lHhGGGGGGGGGGGr? r\r]r^rddictrtrYrkrrs@r'r4r4szFFFFF D    HH]HHHHHr?r4c@eZdZfdZdefdZedZxZS) ClusterLogicsctttjtd|_dSNTr)rrdrrrKrs r'rdzClusterLogics.__init__s6 '''&.}TJJJ r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))rurlapikeyrXrs r'rz&ClusterLogics.show..s4   1111 q111r?rrs r'rtzClusterLogics.showrr?c@K|d=|jjdi|d{VdS)NforcerXr)rcrSs r'rkzClusterLogics.initsB 7Odl))&)))))))))))r?rrs@r'rrssKKKKK D    **]*****r?rceZdZfdZxZS)r=ctttj|jd|_dSr)rrdrrrKr&rs r'rdzSample.__init__s8 (((&.tyFFF r?rrs@r'r=r=sAGGGGGGGGGr?r=cneZdZfdZdefdZed dZd dZde e fdZ de fd Z xZS) r3cttdtjdt jtj|j d|_dS)Nz /var/log//Tr) rrdrrPRODUCTAcronisBackupConfigLOG_NAMErrKr&rs r'rdzAcronis.__init__sX  G $ .A.J.J K   '.tyFFF r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))rrrXrs r'rz Acronis.show..s4   1--- q---r?rrs r'rtz Acronis.showrr?FcK|jd{V }|j||||tjd{VdS)N provisionrtmp_dir)rKis_agent_installedrkrTMPDIRrcrrrrSrs r'rkz Acronis.inits"l==????????? l  K             r?NcFK|j|d{VSr2)rKbackups)rcuntils r' _list_backupszAcronis._list_backupss.\))%000000000r?cK|d{V}|r&ttd|DSdS)Nc3K|];}|jtjVz4Acronis.get_last_backup_timestamp..sWN**(,*??IIKKr?)rr|max)rcrs r'rJz!Acronis.get_last_backup_timestampss**,,,,,,,,  ")  tr?cK t|d{VS#tjtf$rt $rt dYdSwxYw)zif backup exists, than state OKNzError during checking stateF)r{rasyncioCancelledErrorr Exceptionlogger exceptionrps r'rxzAcronis.check_states d002222222233 3&(AB          : ; ; ;55 s&+:A)(A)rzr2)r\r]r^rdrrtrYrkrrr|rJr{rxrrs@r'r3r3sGGGGG D       ] 1111 #    4r?r3c@eZdZdefdZdZdeefdZddZ dS) CloudLinuxBaser)cK|jd{V}|d|d<|jd{V|d<|S)Nusagebackup_space_used_bytes login_url)rKrpoprrs r'rtzCloudLinuxBase.show sr,++-------- /8}}W/E/E +,'+|'='='?'?!?!?!?!?!?!? +r?cHKtd |jd{VdS#t$rX}t jdtt|j r|j drt|ndd}~wwxYw)Nz Making backupzCloudLinux backup failedrr) rrrKmake_initial_backup_strictrloggingrr[lenrRstr)rces r'rvzCloudLinuxBase.make_backups O$$$ ,99;; ; ; ; ; ; ; ; ; ;     8 9 9 9!af++G!&)GA  s? B! ABB!cDK|jd{VSr2)rKget_backup_progressrps r'rz"CloudLinuxBase.get_backup_progresss,\55777777777r?FcKtd|jz|jd{V }|j||||t jd{VdS)NzStarting %s initr)rrr&rKrrkrrrs r'rkzCloudLinuxBase.init s &2333"l==????????? l  K             r?Nrz) r\r]r^rrtrvrr|rrkrXr?r'rr sqD 88C=8888       r?rcZeZdZd\ZZfdZedfd ZGddZej de ffd Z ej fd Z ej de effd Zej de effd Zej deffd Zde fd Zdfd ZxZS)r6)paidunpaidc`tt|_dSr2)rrdr r&rs r'rdzCloudLinux.__init__/s$  r?FcKtjtjd{V}t |d|d|d{VdS)N server_idloginrr)racronis_credentialsr get_server_idrrk)rcrrS credentialsrs r'rkzCloudLinux.init3s#7 .00         ggll   #           r?c$eZdZedZdS)CloudLinux.DecoratorscFtjfd}|S)NcK |g|Ri|d{VS#t$r1|dd{V|g|Ri|d{VcYSwxYw)NTr)rrk)rcrRrSrVs r'wrappedzOCloudLinux.Decorators.update_credentials_on_unauthorized_error..wrappedAs:!"4!9$!9!9!9&!9!999999990:::))$)/////////!"4!9$!9!9!9&!9!9999999999:s8AA) functoolswraps)rVrs` r'(update_credentials_on_unauthorized_errorz>CloudLinux.Decorators.update_credentials_on_unauthorized_error?s8 _Q   : : : :  :Nr?N)r\r]r^ staticmethodrrXr?r' Decoratorsr>s-       r?rr)cKtd{V}tjt jd{V}|dd}|dd}||d<||d<|S)Nrsizerrpurchased_backup_gb resize_url)rrtr acronis_checkrrrF)rcrresponserrrs r'rtzCloudLinux.showKs'',,..((((((  . .00         'll6155\\%.. +> '(", ,r?cXKtd{VdSr2)rrvrs r'rvzCloudLinux.make_backupZs5gg!!###########r?cTKtd{VSr2)rrrs r'rzCloudLinux.get_backup_progress^s/WW00222222222r?cTKtd{VSr2)rrJrs r'rJz$CloudLinux.get_last_backup_timestampbs/WW66888888888r?cTKtd{VSr2)rrxrs r'rxzCloudLinux.check_statefs/WW((*********r?cK tjtjd{V}n3#t$r&}|j|dcYd}~Sd}~wwxYw|j|ddS)Nr)statusrr)rr) rrrrrUNPAIDadd_used_spacePAIDrF)rccontentrs r'rqzCloudLinux.checkjs F#1$244GG F F F"k!2B2B2D2DEE E E E E E E F)W[[-@-@AAAs,1 A!AA!A!cKtd{V|r.tjt jd{VdSdS)Nr)rrnracronis_removerr)rcrmrs r'rnzCloudLinux.disabletswggoo  O(:3K3M3MNNN N N N N N N N N N O Or?rz)r\r]r^rrrdrYrkrrrrtrvrr|rrJr{rxrqrnrrs@r'r6r6,s#LD&     ]         8 D     98 8$$$$98$838C=3333398389#999999898+4+++++98+BTBBBBOOOOOOOOOOr?r6c8eZdZfdZefdZxZS)r8c`tt|_dSr2)rrdrr&rs r'rdzCloudLinuxOnPremise.__init__{s$ ) r?cJKtj|i|d{VdSr2)rrk)rcrRrSrs r'rkzCloudLinuxOnPremise.inits:egglD+F+++++++++++r?)r\r]r^rdrYrkrrs@r'r8r8zs]*****,,,,],,,,,r?r8)FT)Drrrdatetimertypingrrrr defence360agent.contracts.configrr r rr r r rrrrrrrr!defence360agent.contracts.licenserdefence360agent.internals.clnrr*defence360agent.subsys.panels.cpanel.panelr/defence360agent.subsys.panels.directadmin.panelr)defence360agent.subsys.panels.plesk.panelrrestore_infectedr(restore_infected.backup_backends.acronisr$restore_infected.backup_backends_librr getLoggerr\rr(rr0r"rHr|rJrYrr[r`r;r:r<r4rr=r3rr6r8rXr?r'rs111111111111 988888AAAAAAAA======GGGGGG;;;;;;000000EEEEEE  8 $ $LLL d3i     #x-4=Xc]==== >#>>>>        i   """"""""J     ,   !!!!!<!!! &&&&& &&& HHHHH\HHH$*****L***,GGGGG\GGG 11111l111h     W   @KOKOKOKOKOKOKOKO\,,,,,.,,,,,r?defence360agent/subsys/__pycache__/backup_systems.cpython-311.pyc0000644000000000000000000006204000000000000022046 0ustar r_j,ddlZddlZddlZddlmZddlmZmZmZm Z ddl m Z m Z m ZmZmZmZmZmZmZmZmZmZmZmZddlmZddlmZmZddlm Z ddl!m"Z"dd l#m$Z$e sdd l%m&Z&dd l'm(Z(dd l)m*Z*m+Z+ej,e-Z.d Z/dee0fdZ1 d.dee0effdZ2de e0fdZ3de e4fdZ5dZ6Gdde7Z8GddZ9Gdde9Z:Gdde9Z;Gdde9Z<Gd d!e9Z=Gd"d#e9Z>Gd$d%e9Z?Gd&d'e9Z@Gd(d)e@ZAGd*d+eAZBGd,d-eAZCdS)/N)timezone)CallableDictListOptional)ACRONISANTIVIRUS_MODE AcronisBackup BackupConfig BackupRestore CLOUDLINUXCLOUDLINUX_ON_PREMISE CLUSTERLOGICSCPANELCore DIRECTADMINPLESKR1SOFTSAMPLE_BACKEND) LicenseCLN)BackupNotFoundRestCLN)cPanel) DirectAdmin)Plesk)backup_backends) BackupFailed)BackendNonApplicableErrorBackendNotAuthorizedErrorc td|S#ttf$r#td|wxYw)NT)include_samplez"Backup system is not available: {})_get_avalible_backendsKeyErrorr ValueErrorformat)names Z/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/backup_systems.py get_backendr((siL@%T:::4@BBB / 0LLL=DDTJJKKKLs  4Areturncg}tdD]5\}} |||&#t$rY2wxYw|S)NF) include_cl)r"itemsappendr)namesr&clss r'get_available_backends_namesr0/s E+u===CCEE c  CEEE LL    )    D  Ls A  AAFTctttti}tjr|rt |t<tjrt|t<tj rt|t<nEtj rt |t"<n"t%j rt&|t(<|rt*|t,<|SN)rAcronisrR1Softr CL_BACKUP_ALLOWED CloudLinuxr CL_ON_PREMISE_BACKUP_ALLOWEDCloudLinuxOnPremiserr is_installed cPanelBackuprr PleskBackuprrDirectAdminBackuprSampler)r!r+backendss r'r"r"=s H &*:*)1>*=&' 2'    2%  ! # #2 1*#) Octdi}|do|dSN BACKUP_SYSTEMenabled backup_system)r config_to_dictget)confs r'get_current_backendrHWsJ >> ( ( * * . . C CD 88I   <488O#<#<.wrapperfs  3q.t...v........BB  & &r & 2 2 2 2C & &r & 2 2 2 2 s 2A )rVrWs` r' transactionalrYes# Nr?ceZdZdS)BackupExceptionN)__name__ __module__ __qualname__rXr?r'r[r[rsDr?r[c`eZdZd dZdZdZddZdZdZd Z d e fd Z d e e fd ZdS) BackupSystemNc"||_||_dSr2)r&log_path)selfr&rbs r'__init__zBackupSystem.__init__ws   r?cnd||r|jnddi}t|dddS)NrB)rCrDT) overwritevalidate)r&r dict_to_config)rcrCnew_confs r'rQz#BackupSystem._update_backups_config{sN ".5!?4  %%h$%NNNNNr?c6K|ddS)NTrOrP)rcrRrSs r'initzBackupSystem.inits# ##D#11111r?Fc6K|ddS)NFrOrP)rcdelete_backupss r'disablezBackupSystem.disables# ##E#22222r?c KiSr2rXrcs r'checkzBackupSystem.check  r?c KiSr2rXrps r'showzBackupSystem.showrrr?c KdSr2rXrps r' make_backupzBackupSystem.make_backups  r?r)cKtdi}|do|d|jkSrA)r rErFr&)rcrGs r' check_statezBackupSystem.check_statesU~~,,..22?BGGxx ""Mtxx'@'@DI'MMr?c KdSr2rXrps r'rJz&BackupSystem.get_last_backup_timestamps tr?r2F)r\r]r^rdrQrkrnrqrtrvboolrxrintrJrXr?r'r`r`vs!!!!OOO2223333   N4NNNN#r?r`ceZdZfdZxZS)r;cTttdSr2)superrdrrc __class__s r'rdzPleskBackup.__init__s! r?r\r]r^rd __classcell__rs@r'r;r;s8         r?r;ceZdZfdZxZS)r:cTttdSr2)rrdrrs r'rdzcPanelBackup.__init__s!      r?rrs@r'r:r:s8!!!!!!!!!r?r:ceZdZfdZxZS)r<cTttdSr2)rrdrrs r'rdzDirectAdminBackup.__init__s! %%%%%r?rrs@r'r<r<s8&&&&&&&&&r?r<c@eZdZfdZdefdZedZxZS)r4ctttjdd|_dS)Nr1softTasync_)rrdrrrKrs r'rdzR1Soft.__init__s6    &.xEEE r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))username timestampiprX.0kvs r' zR1Soft.show..s4   1333 q333r?rKinfor,rc info_datas r'rtz R1Soft.showV,++--------   !))    r?cPK|j||||d{VdSr2rKrk)rcrrpasswordencryption_keyrSs r'rkz R1Soft.inits:lHhGGGGGGGGGGGr? r\r]r^rddictrtrYrkrrs@r'r4r4szFFFFF D    HH]HHHHHr?r4c@eZdZfdZdefdZedZxZS) ClusterLogicsctttjtd|_dSNTr)rrdrrrKrs r'rdzClusterLogics.__init__s6 '''&.}TJJJ r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))rurlapikeyrXrs r'rz&ClusterLogics.show..s4   1111 q111r?rrs r'rtzClusterLogics.showrr?c@K|d=|jjdi|d{VdS)NforcerXr)rcrSs r'rkzClusterLogics.initsB 7Odl))&)))))))))))r?rrs@r'rrssKKKKK D    **]*****r?rceZdZfdZxZS)r=ctttj|jd|_dSr)rrdrrrKr&rs r'rdzSample.__init__s8 (((&.tyFFF r?rrs@r'r=r=sAGGGGGGGGGr?r=cneZdZfdZdefdZed dZd dZde e fdZ de fd Z xZS) r3cttdtjdt jtj|j d|_dS)Nz /var/log//Tr) rrdrrPRODUCTAcronisBackupConfigLOG_NAMErrKr&rs r'rdzAcronis.__init__sX  G $ .A.J.J K   '.tyFFF r?r)cK|jd{V}d|DS)Nc"i|] \}}|dv || S))rrrXrs r'rz Acronis.show..s4   1--- q---r?rrs r'rtz Acronis.showrr?FcK|jd{V }|j||||tjd{VdS)N provisionrtmp_dir)rKis_agent_installedrkrTMPDIRrcrrrrSrs r'rkz Acronis.inits"l==????????? l  K             r?NcFK|j|d{VSr2)rKbackups)rcuntils r' _list_backupszAcronis._list_backupss.\))%000000000r?cK|d{V}|r&ttd|DSdS)Nc3K|];}|jtjVz4Acronis.get_last_backup_timestamp..sWN**(,*??IIKKr?)rr|max)rcrs r'rJz!Acronis.get_last_backup_timestampss**,,,,,,,,  ")  tr?cK t|d{VS#tjtf$rt $rt dYdSwxYw)zif backup exists, than state OKNzError during checking stateF)r{rasyncioCancelledErrorr Exceptionlogger exceptionrps r'rxzAcronis.check_states d002222222233 3&(AB          : ; ; ;55 s&+:A)(A)rzr2)r\r]r^rdrrtrYrkrrr|rJr{rxrrs@r'r3r3sGGGGG D       ] 1111 #    4r?r3c@eZdZdefdZdZdeefdZddZ dS) CloudLinuxBaser)cK|jd{V}|d|d<|jd{V|d<|S)Nusagebackup_space_used_bytes login_url)rKrpoprrs r'rtzCloudLinuxBase.show sr,++-------- /8}}W/E/E +,'+|'='='?'?!?!?!?!?!?!? +r?cHKtd |jd{VdS#t$rX}t jdtt|j r|j drt|ndd}~wwxYw)Nz Making backupzCloudLinux backup failedrr) rrrKmake_initial_backup_strictrloggingrr[lenrRstr)rces r'rvzCloudLinuxBase.make_backups O$$$ ,99;; ; ; ; ; ; ; ; ; ;     8 9 9 9!af++G!&)GA  s? B! ABB!cDK|jd{VSr2)rKget_backup_progressrps r'rz"CloudLinuxBase.get_backup_progresss,\55777777777r?FcKtd|jz|jd{V }|j||||t jd{VdS)NzStarting %s initr)rrr&rKrrkrrrs r'rkzCloudLinuxBase.init s &2333"l==????????? l  K             r?Nrz) r\r]r^rrtrvrr|rrkrXr?r'rr sqD 88C=8888       r?rcZeZdZd\ZZfdZedfd ZGddZej de ffd Z ej fd Z ej de effd Zej de effd Zej deffd Zde fd Zdfd ZxZS)r6)paidunpaidc`tt|_dSr2)rrdr r&rs r'rdzCloudLinux.__init__/s$  r?FcKtjtjd{V}t |d|d|d{VdS)N server_idloginrr)racronis_credentialsr get_server_idrrk)rcrrS credentialsrs r'rkzCloudLinux.init3s#7 .00         ggll   #           r?c$eZdZedZdS)CloudLinux.DecoratorscFtjfd}|S)NcK |g|Ri|d{VS#t$r1|dd{V|g|Ri|d{VcYSwxYw)NTr)rrk)rcrRrSrVs r'wrappedzOCloudLinux.Decorators.update_credentials_on_unauthorized_error..wrappedAs:!"4!9$!9!9!9&!9!999999990:::))$)/////////!"4!9$!9!9!9&!9!9999999999:s8AA) functoolswraps)rVrs` r'(update_credentials_on_unauthorized_errorz>CloudLinux.Decorators.update_credentials_on_unauthorized_error?s8 _Q   : : : :  :Nr?N)r\r]r^ staticmethodrrXr?r' Decoratorsr>s-       r?rr)cKtd{V}tjt jd{V}|dd}|dd}||d<||d<|S)Nrsizerrpurchased_backup_gb resize_url)rrtr acronis_checkrrrF)rcrresponserrrs r'rtzCloudLinux.showKs'',,..((((((  . .00         'll6155\\%.. +> '(", ,r?cXKtd{VdSr2)rrvrs r'rvzCloudLinux.make_backupZs5gg!!###########r?cTKtd{VSr2)rrrs r'rzCloudLinux.get_backup_progress^s/WW00222222222r?cTKtd{VSr2)rrJrs r'rJz$CloudLinux.get_last_backup_timestampbs/WW66888888888r?cTKtd{VSr2)rrxrs r'rxzCloudLinux.check_statefs/WW((*********r?cK tjtjd{V}n3#t$r&}|j|dcYd}~Sd}~wwxYw|j|ddS)Nr)statusrr)rr) rrrrrUNPAIDadd_used_spacePAIDrF)rccontentrs r'rqzCloudLinux.checkjs F#1$244GG F F F"k!2B2B2D2DEE E E E E E E F)W[[-@-@AAAs,1 A!AA!A!cKtd{V|r.tjt jd{VdSdS)Nr)rrnracronis_removerr)rcrmrs r'rnzCloudLinux.disabletswggoo  O(:3K3M3MNNN N N N N N N N N N O Or?rz)r\r]r^rrrdrYrkrrrrtrvrr|rrJr{rxrqrnrrs@r'r6r6,s#LD&     ]         8 D     98 8$$$$98$838C=3333398389#999999898+4+++++98+BTBBBBOOOOOOOOOOr?r6c8eZdZfdZefdZxZS)r8c`tt|_dSr2)rrdrr&rs r'rdzCloudLinuxOnPremise.__init__{s$ ) r?cJKtj|i|d{VdSr2)rrk)rcrRrSrs r'rkzCloudLinuxOnPremise.inits:egglD+F+++++++++++r?)r\r]r^rdrYrkrrs@r'r8r8zs]*****,,,,],,,,,r?r8)FT)Drrrdatetimertypingrrrr defence360agent.contracts.configrr r rr r r rrrrrrrr!defence360agent.contracts.licenserdefence360agent.internals.clnrr*defence360agent.subsys.panels.cpanel.panelr/defence360agent.subsys.panels.directadmin.panelr)defence360agent.subsys.panels.plesk.panelrrestore_infectedr(restore_infected.backup_backends.acronisr$restore_infected.backup_backends_librr getLoggerr\rr(rr0r"rHr|rJrYrr[r`r;r:r<r4rr=r3rr6r8rXr?r'rs111111111111 988888AAAAAAAA======GGGGGG;;;;;;000000EEEEEE  8 $ $LLL d3i     #x-4=Xc]==== >#>>>>        i   """"""""J     ,   !!!!!<!!! &&&&& &&& HHHHH\HHH$*****L***,GGGGG\GGG 11111l111h     W   @KOKOKOKOKOKOKOKO\,,,,,.,,,,,r?defence360agent/subsys/__pycache__/clcagefs.cpython-311.opt-1.pyc0000644000000000000000000003122600000000000021522 0ustar r_j'ddlZddlZddlZdZdZGddeZGddZdZdd Z dd Z dZ dZ ddZ dS)Nz/etc/cagefs/cagefs.mpz/usr/sbin/cagefsctlceZdZdZdZdS)CagefsMpConflictc2d|dtd|d|_dS)NzConflict in adding 'z' to z5 because of pre-existing alternative specification: '')CAGEFS_MP_FILENAME_msg)selfnew_item existing_items T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/clcagefs.py__init__zCagefsMpConflict.__init__s,xx+++]]] < c|jSN)rr s r __str__zCagefsMpConflict.__str__s yrN)__name__ __module__ __qualname__r rrr rrs2   rrceZdZdZdZdZdZdZdZe dZ dZ d Z d Z e d Ze d Ze d ZdZdZdZdS) CagefsMpItems@!%r!c|dddkr d|_dS|dkr d|_dS||_dS)zConstructor :param arg: Is either path to add to cagefs.mp or a raw line is read from cagefs.mp :param prefix: The same as adding prefix '!' to arg before passing it to ctorN#r) _path_specstrip)r args r r zCagefsMpItem.__init__#sI rr7d??"DOOO YY[[C  "DOOO!DOOOrc\|dkr|d|j|fz|_|S)z%Specify mode as in fluent constructor@Ns%s,%03o)prefixr)r modes r r#zCagefsMpItem.mode2s4 ;;==D T%5(DOT+BBDO rc4tj|jSr)osfsdecoderrs r rzCagefsMpItem.__str__:s{4?+++rc8|dkrdS|ddkr|dzS|S)Nr//r)paths r _add_slashzCagefsMpItem._add_slash=s. 3;;4 8w  $;  rcdt|}|s|rdSt|}t|}||S)NF)r_adoptis_dummyr,r+ startswith)r anotheradopted this_pathtest_preexist_in_paths r pre_exist_inzCagefsMpItem.pre_exist_inEs%%g.. ==?? g..00 5 ++DIIKK88 , 7 7  G G##$9:::rct|}|s|rdS||krdStjtjgi}g}||||vS)NFT)rr.r/r"_PREFIX_MOUNT_RW_PREFIX_MOUNT_ROget)r existingr2prefix_compatibility_map null_optionss r is_compatible_by_prefix_withz)CagefsMpItem.is_compatible_by_prefix_withPs%%h// ==?? g..00 5 ;;==GNN,, , ,4  )L,I+J$   {{}} 8 < < NN  l! !   rc|jduSrrrs r r/zCagefsMpItem.is_dummycs$&&rcNt|tr|St|Sr) isinstancer)xs r r.zCagefsMpItem._adoptfs% a & & #H?? "rc8|ddS)zjCut off mode from path spec like @/var/run/screen,777 Only one comma per path spec is allowed ;-),r)split path_specs r _cut_off_modezCagefsMpItem._cut_off_modems t$$Q''rc@|tjSr)lstripr PREFIX_LISTrFs r _cut_off_prefixzCagefsMpItem._cut_off_prefixus 8999rcptt|jSr)rrLrHrrs r r+zCagefsMpItem.pathys-++  & &t 7 7   rc^|j|kr|jddSdS)Nrrr)rr+rs r r"zCagefsMpItem.prefix~s- ?diikk ) )?1Q3' '3rc|jSrr?rs r speczCagefsMpItem.specs rN)rrrrKr7r8r r#r staticmethodr,r5r=r/r.rHrLr+r"rPrrr rrsK " " ",,,\ ; ; ;   &'''##\# ((\(::\:    rrcJtjtSr)r%r+existsCAGEFSCTL_TOOLrrr is_cagefs_presentrUs 7>>. ) ))rc|d}|d}tj|stj||tj||tj|||dS)Nr))r%r+isdirmkdirchmodchown)r+r#owner_idgroup_ids r _mk_mount_dir_setup_permr^sm 7==     tHT8X&&&&&rrTc t||||tjtst jtdgt jtdgttd} t||z | d|D} fd|D} | s| dd| dd }| d |d zd z| d z||rt jtd gn1 | dst% | d|dS#|wxYw)a  Add mount point to /etc/cagefs/cagefs.mp :param path: Directory path to be added in cagefs.mp and mounted from within setup_mount_dir_cagefs(). If this directory does not exist, then it is created. :param added_by: package or component, mount dir relates to, or whatever will stay in cagefs.mp with "# added by..." comment :param mode: If is not None: Regardless of whether directory exists or not prior this call, it's permissions will be set to mode. :param owner_id: Regardless of whether directory exists or not prior this call, it's owner id will be set to. If None, the owner won't be changed. :param group_id: Regardless of whether directory exists or not prior this call, it's group id will be set to. If None, the group won't be changed. :param prefix: Mount point prefix. Default is mount as RW. Pass '!' to add read-only mount point. Refer CageFS section at http://docs.cloudlinux.com/ for more options. :param remount_cagefs: If True, cagefs skeleton will be automatically remounted to apply changes. :returns: None Propagates native EnvironmentError if no CageFS installed or something else goes wrong. Raises CagefsMpConflict if path is already specified in cagefs.mp, but in a way which is opposite to mount_as_readonly param. z --create-mpz --check-mpzrb+c3>K|]}|VdSr)rstrip).0 file_lines r z)setup_mount_dir_cagefs..s.FFy ((**FFFFFFrc>g|]}||Sr)r5)rbrBr s r z*setup_mount_dir_cagefs..s<   x'<'>, - -97888O^\2333 '//I ..33D99FFIFFF     #     C NN1a  ''c22H OO+hoog.F.FFN    OOHMMOOe3 4 4 4 OO    C ABBB667G7KLL C"8-=b-ABB B  s D(GGcttd5}|cdddS#1swxYwYdS)Nrb)rnr readlines)fs r _get_cagefs_mp_linesr~s  $ ' '1{{}}s 7;;cttd5}||cdddS#1swxYwYdS)Nwb)rnr writelines)linesr}s r _write_cagefs_mp_linesrs  $ ' '#1||E""##################s 8<<ct}tjdtjtj|fzfd|D}t ||rtjtdgdSdS)z Remove mount points matching given path from cagefs.mp file :param str path: Path that should be removed from file. :param bool remount_cagefs: Remount cagefs skeleton or not :return: Nothing s^[%s]?%s(,\d+)?$c3FK|]}||VdSr)match)rbliners r rdz*remove_mount_dir_cagefs.. s2LLaggdmmLLLLLLLrrkN) r~recompilerrKescaperrlrmrT)r+rurlines_with_excluded_pathrs @r remove_mount_dir_cagefsrs ! " "E  8")D//JJ  A MLLLLLL3444;9:::::;;r)rVNN)rVNNrT)T)r%rrlrrT ExceptionrrrUr^ryr~rrrrr rs ,&     y   ggggggggT*** ' ' ' '&    VVVVr ### ;;;;;;rdefence360agent/subsys/__pycache__/clcagefs.cpython-311.pyc0000644000000000000000000003122600000000000020563 0ustar r_j'ddlZddlZddlZdZdZGddeZGddZdZdd Z dd Z dZ dZ ddZ dS)Nz/etc/cagefs/cagefs.mpz/usr/sbin/cagefsctlceZdZdZdZdS)CagefsMpConflictc2d|dtd|d|_dS)NzConflict in adding 'z' to z5 because of pre-existing alternative specification: '')CAGEFS_MP_FILENAME_msg)selfnew_item existing_items T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/clcagefs.py__init__zCagefsMpConflict.__init__s,xx+++]]] < c|jSN)rr s r __str__zCagefsMpConflict.__str__s yrN)__name__ __module__ __qualname__r rrr rrs2   rrceZdZdZdZdZdZdZdZe dZ dZ d Z d Z e d Ze d Ze d ZdZdZdZdS) CagefsMpItems@!%r!c|dddkr d|_dS|dkr d|_dS||_dS)zConstructor :param arg: Is either path to add to cagefs.mp or a raw line is read from cagefs.mp :param prefix: The same as adding prefix '!' to arg before passing it to ctorN#r) _path_specstrip)r args r r zCagefsMpItem.__init__#sI rr7d??"DOOO YY[[C  "DOOO!DOOOrc\|dkr|d|j|fz|_|S)z%Specify mode as in fluent constructor@Ns%s,%03o)prefixr)r modes r r#zCagefsMpItem.mode2s4 ;;==D T%5(DOT+BBDO rc4tj|jSr)osfsdecoderrs r rzCagefsMpItem.__str__:s{4?+++rc8|dkrdS|ddkr|dzS|S)Nr//r)paths r _add_slashzCagefsMpItem._add_slash=s. 3;;4 8w  $;  rcdt|}|s|rdSt|}t|}||S)NF)r_adoptis_dummyr,r+ startswith)r anotheradopted this_pathtest_preexist_in_paths r pre_exist_inzCagefsMpItem.pre_exist_inEs%%g.. ==?? g..00 5 ++DIIKK88 , 7 7  G G##$9:::rct|}|s|rdS||krdStjtjgi}g}||||vS)NFT)rr.r/r"_PREFIX_MOUNT_RW_PREFIX_MOUNT_ROget)r existingr2prefix_compatibility_map null_optionss r is_compatible_by_prefix_withz)CagefsMpItem.is_compatible_by_prefix_withPs%%h// ==?? g..00 5 ;;==GNN,, , ,4  )L,I+J$   {{}} 8 < < NN  l! !   rc|jduSrrrs r r/zCagefsMpItem.is_dummycs$&&rcNt|tr|St|Sr) isinstancer)xs r r.zCagefsMpItem._adoptfs% a & & #H?? "rc8|ddS)zjCut off mode from path spec like @/var/run/screen,777 Only one comma per path spec is allowed ;-),r)split path_specs r _cut_off_modezCagefsMpItem._cut_off_modems t$$Q''rc@|tjSr)lstripr PREFIX_LISTrFs r _cut_off_prefixzCagefsMpItem._cut_off_prefixus 8999rcptt|jSr)rrLrHrrs r r+zCagefsMpItem.pathys-++  & &t 7 7   rc^|j|kr|jddSdS)Nrrr)rr+rs r r"zCagefsMpItem.prefix~s- ?diikk ) )?1Q3' '3rc|jSrr?rs r speczCagefsMpItem.specs rN)rrrrKr7r8r r#r staticmethodr,r5r=r/r.rHrLr+r"rPrrr rrsK " " ",,,\ ; ; ;   &'''##\# ((\(::\:    rrcJtjtSr)r%r+existsCAGEFSCTL_TOOLrrr is_cagefs_presentrUs 7>>. ) ))rc|d}|d}tj|stj||tj||tj|||dS)Nr))r%r+isdirmkdirchmodchown)r+r#owner_idgroup_ids r _mk_mount_dir_setup_permr^sm 7==     tHT8X&&&&&rrTc t||||tjtst jtdgt jtdgttd} t||z | d|D} fd|D} | s| dd| dd }| d |d zd z| d z||rt jtd gn1 | dst% | d|dS#|wxYw)a  Add mount point to /etc/cagefs/cagefs.mp :param path: Directory path to be added in cagefs.mp and mounted from within setup_mount_dir_cagefs(). If this directory does not exist, then it is created. :param added_by: package or component, mount dir relates to, or whatever will stay in cagefs.mp with "# added by..." comment :param mode: If is not None: Regardless of whether directory exists or not prior this call, it's permissions will be set to mode. :param owner_id: Regardless of whether directory exists or not prior this call, it's owner id will be set to. If None, the owner won't be changed. :param group_id: Regardless of whether directory exists or not prior this call, it's group id will be set to. If None, the group won't be changed. :param prefix: Mount point prefix. Default is mount as RW. Pass '!' to add read-only mount point. Refer CageFS section at http://docs.cloudlinux.com/ for more options. :param remount_cagefs: If True, cagefs skeleton will be automatically remounted to apply changes. :returns: None Propagates native EnvironmentError if no CageFS installed or something else goes wrong. Raises CagefsMpConflict if path is already specified in cagefs.mp, but in a way which is opposite to mount_as_readonly param. z --create-mpz --check-mpzrb+c3>K|]}|VdSr)rstrip).0 file_lines r z)setup_mount_dir_cagefs..s.FFy ((**FFFFFFrc>g|]}||Sr)r5)rbrBr s r z*setup_mount_dir_cagefs..s<   x'<'>, - -97888O^\2333 '//I ..33D99FFIFFF     #     C NN1a  ''c22H OO+hoog.F.FFN    OOHMMOOe3 4 4 4 OO    C ABBB667G7KLL C"8-=b-ABB B  s D(GGcttd5}|cdddS#1swxYwYdS)Nrb)rnr readlines)fs r _get_cagefs_mp_linesr~s  $ ' '1{{}}s 7;;cttd5}||cdddS#1swxYwYdS)Nwb)rnr writelines)linesr}s r _write_cagefs_mp_linesrs  $ ' '#1||E""##################s 8<<ct}tjdtjtj|fzfd|D}t ||rtjtdgdSdS)z Remove mount points matching given path from cagefs.mp file :param str path: Path that should be removed from file. :param bool remount_cagefs: Remount cagefs skeleton or not :return: Nothing s^[%s]?%s(,\d+)?$c3FK|]}||VdSr)match)rbliners r rdz*remove_mount_dir_cagefs.. s2LLaggdmmLLLLLLLrrkN) r~recompilerrKescaperrlrmrT)r+rurlines_with_excluded_pathrs @r remove_mount_dir_cagefsrs ! " "E  8")D//JJ  A MLLLLLL3444;9:::::;;r)rVNN)rVNNrT)T)r%rrlrrT ExceptionrrrUr^ryr~rrrrr rs ,&     y   ggggggggT*** ' ' ' '&    VVVVr ### ;;;;;;rdefence360agent/subsys/__pycache__/notifier.cpython-311.opt-1.pyc0000644000000000000000000000664100000000000021575 0ustar r_jIdZddlZddlZddlZdZdZdZdZdZdZ d Z d Z d Z d Z d ZdZdedededefdZdeddfdZdedededdfdZddZdS)z$Send events via Notification serviceNz/opt/imunify360/lib/event.sockg$@iCONFIG_UPDATEDUSER_SCAN_STARTEDUSER_SCAN_FINISHEDUSER_SCAN_MALWARE_FOUNDCUSTOM_SCAN_STARTEDCUSTOM_SCAN_FINISHEDCUSTOM_SCAN_MALWARE_FOUNDSCRIPT_BLOCKEDevent_iduserbodyreturnc tj||tjtj|ddd}|d}t |tkr5td t |tt | td|zS)Nzutf-8)r r rz#message size {} exceeds limit of {}big) byteorder) jsondumpsbase64 b64encodeencodedecodelen _MAX_SIZE Exceptionformatto_bytes _LEN_BYTES)r r reventbinarys T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/notifier.py_prepare_eventr"s J $TZ%5%5%<%Send an event with given event_id and user, having given body.N)r"r%wait_forr-SOCKET_TIMEOUT)r r rrs r! trigger_eventr13sJ 8T4 0 0E  ;u--~ > >>>>>>>>>>r#cBKttdid{VdS)zRSend CONFIG_UPDATED event. This forces imunify-notifier to reread its config.N)r1CONFIG_UPDATED_EVENT_IDr#r!config_updatedr69s3 /R 8 8888888888r#)rN)__doc__r%rrr'r0rrr4USER_SCAN_STARTED_EVENT_IDUSER_SCAN_FINISHED_EVENT_ID USER_SCAN_MALWARE_FOUND_EVENT_IDCUSTOM_SCAN_STARTED_EVENT_IDCUSTOM_SCAN_FINISHED_EVENT_ID"CUSTOM_SCAN_MALWARE_FOUND_EVENT_IDSCRIPT_BLOCKED_EVENT_IDstrdictbytesr"r-r1r6r5r#r!rBs** .    *02#< 4 6%@"*FSFF4FEFFFF(Ut?#?S?????? 999999r#defence360agent/subsys/__pycache__/notifier.cpython-311.pyc0000644000000000000000000000664100000000000020636 0ustar r_jIdZddlZddlZddlZdZdZdZdZdZdZ d Z d Z d Z d Z d ZdZdedededefdZdeddfdZdedededdfdZddZdS)z$Send events via Notification serviceNz/opt/imunify360/lib/event.sockg$@iCONFIG_UPDATEDUSER_SCAN_STARTEDUSER_SCAN_FINISHEDUSER_SCAN_MALWARE_FOUNDCUSTOM_SCAN_STARTEDCUSTOM_SCAN_FINISHEDCUSTOM_SCAN_MALWARE_FOUNDSCRIPT_BLOCKEDevent_iduserbodyreturnc tj||tjtj|ddd}|d}t |tkr5td t |tt | td|zS)Nzutf-8)r r rz#message size {} exceeds limit of {}big) byteorder) jsondumpsbase64 b64encodeencodedecodelen _MAX_SIZE Exceptionformatto_bytes _LEN_BYTES)r r reventbinarys T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/notifier.py_prepare_eventr"s J $TZ%5%5%<%Send an event with given event_id and user, having given body.N)r"r%wait_forr-SOCKET_TIMEOUT)r r rrs r! trigger_eventr13sJ 8T4 0 0E  ;u--~ > >>>>>>>>>>r#cBKttdid{VdS)zRSend CONFIG_UPDATED event. This forces imunify-notifier to reread its config.N)r1CONFIG_UPDATED_EVENT_IDr#r!config_updatedr69s3 /R 8 8888888888r#)rN)__doc__r%rrr'r0rrr4USER_SCAN_STARTED_EVENT_IDUSER_SCAN_FINISHED_EVENT_ID USER_SCAN_MALWARE_FOUND_EVENT_IDCUSTOM_SCAN_STARTED_EVENT_IDCUSTOM_SCAN_FINISHED_EVENT_ID"CUSTOM_SCAN_MALWARE_FOUND_EVENT_IDSCRIPT_BLOCKED_EVENT_IDstrdictbytesr"r-r1r6r5r#r!rBs** .    *02#< 4 6%@"*FSFF4FEFFFF(Ut?#?S?????? 999999r#defence360agent/subsys/__pycache__/persistent_state.cpython-311.opt-1.pyc0000644000000000000000000000722100000000000023351 0ustar r_jddlZddlmZddlmZddlmZddlmZddl m Z ee Z edZ e dz ZeZd ed ee je je jfd efd Zd edefdZd efdZdZdS)N) getLogger)Path)Literal)ANTIVIRUS_MODE)Scopez/var/imunify360z.persistent_state lock_filescopereturnc>td|dz }|tjkrt|nc|tjkr"t rt|n1|tjkr!t st||S)z%Register lock file for further usage..z.lock)PERSISTENT_STATE_DIRrAV_IM360 LOCK_FILESaddAVrIM360)rr _lock_files \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/persistent_state.pyregister_lock_filers&(t}||dz }|rm tj|dS#tjt tf$r&}t d||Yd}~nd}~wwxYwtS)z3Load state from a file in .persistent_state folder.rrzFailed to load state: %s %sN) r existsrloadr!JSONDecodeErrorr#UnicodeDecodeErrorr$r%dict)rr&r'r(s r load_stater1*s'K3333IG G9Y^^C0011 1$g/AB G G G LL6 A F F F F F F F F G 66Ms&A B(B  BcztdD]}|tvr| dS)z;Remove all unused lock files from .persistent_state folder.z*.lockN)r globrunlink)rs rremove_unused_locksr58sG)..x88 J & &      r)rloggingrpathlibrtypingr defence360agent.contracts.configr!defence360agent.contracts.pluginsr__name__r$BASE_DIRr setrstrrrrrr0r)r1r5rrr@s: ;;;;;;333333 8   4! " ""55 SUU   "58U[%.#HI       C3 C C C C C d    rdefence360agent/subsys/__pycache__/persistent_state.cpython-311.pyc0000644000000000000000000000722100000000000022412 0ustar r_jddlZddlmZddlmZddlmZddlmZddl m Z ee Z edZ e dz ZeZd ed ee je je jfd efd Zd edefdZd efdZdZdS)N) getLogger)Path)Literal)ANTIVIRUS_MODE)Scopez/var/imunify360z.persistent_state lock_filescopereturnc>td|dz }|tjkrt|nc|tjkr"t rt|n1|tjkr!t st||S)z%Register lock file for further usage..z.lock)PERSISTENT_STATE_DIRrAV_IM360 LOCK_FILESaddAVrIM360)rr _lock_files \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/persistent_state.pyregister_lock_filers&(t}||dz }|rm tj|dS#tjt tf$r&}t d||Yd}~nd}~wwxYwtS)z3Load state from a file in .persistent_state folder.rrzFailed to load state: %s %sN) r existsrloadr!JSONDecodeErrorr#UnicodeDecodeErrorr$r%dict)rr&r'r(s r load_stater1*s'K3333IG G9Y^^C0011 1$g/AB G G G LL6 A F F F F F F F F G 66Ms&A B(B  BcztdD]}|tvr| dS)z;Remove all unused lock files from .persistent_state folder.z*.lockN)r globrunlink)rs rremove_unused_locksr58sG)..x88 J & &      r)rloggingrpathlibrtypingr defence360agent.contracts.configr!defence360agent.contracts.pluginsr__name__r$BASE_DIRr setrstrrrrrr0r)r1r5rrr@s: ;;;;;;333333 8   4! " ""55 SUU   "58U[%.#HI       C3 C C C C C d    rdefence360agent/subsys/__pycache__/svcctl.cpython-311.opt-1.pyc0000644000000000000000000002605200000000000021252 0ustar r_j4ddlZddlZddlZddlZddlmZddlmZddl m Z m Z m Z m Z ejeZdZdZdZdZd Zd Zd Zd ed fdZGdd ZGddeZGddeZdZdZdZdZdZ dZ!dZ"dZ#dZ$dS)N)Iterable)Core) check_run CheckRunErrorrun OsReleaseInfozimunify360-dos-protectionz imunify360-unified-access-loggerzimunify360-pamzimunify-auditd-log-readerzimunify360-scanlogdzimunify360-agentcfd}|S)Nc|K|i|}td|t|d{VdS)Nzcheck_call(%r))loggerdebugr)argskwargscmdfuncs R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/svcctl.pywrapperz_apply_cmd..wrappersTdD#F## %s+++nn)rrs` r _apply_cmdrs# Nrservices_SystemctlBasedcK|D]} |d{V|d{Vn5#t$r(}td||Yd}~dSd}~wwxYwt dD]T}|d{Vrn7td|dtjdd{VUdS)Nz/Failed to reset failed state for service %s: %s z4Service %s is still not active, sleep for %s seconds) reset_failedrestartrr warningrange is_activeasynciosleep)rse_s r_reset_failed_stater%s;## .."" " " " " " " "))++            NNA1a    FFFFFF   r # #A[[]]""""""  NNF1   -"" " " " " " " " "##s4= A/A**A/ceZdZdZdZedZedZedZede fdZ de fdZ d Z d Z ede fd Zed Zd ZedZdZdS)r systemctlc||_dSN) _service_name)self service_names r__init__z_SystemctlBased.__init__5s)rc |jd|jgS)Nstart SVC_CTL_BINr*r+s rr/z_SystemctlBased.start8s '4+=>>rc |jd|jgS)Nstopr0r2s rr4z_SystemctlBased.stop<s &$*<==rc |jd|jgS)Nrr0r2s rrz_SystemctlBased.restart@s )T-?@@rnowc0|jdg|rdgng|jS)Nenable--nowr0r+r6s r _enable_nowz_SystemctlBased._enable_nowDs9    &wiiB      rcjK||d{Vi} tj|n#ttf$rYdSwxYw|dddkrdS|dddkr|d{VdSdS)N)r6IDubuntu VERSION_IDz16.04)r;rdict_from_fileFileNotFoundErrorPermissionErrorgetlowerr)r+r6osinfos rr8z_SystemctlBased.enableMs3'''''''''    ( 0 0 0 0!?3    FF  ::dB   % % ' '8 3 3 F ::lB ' '7 2 2,,..  3 2s7A  A cK|jd|jg}tj|tjtjdd{V}|d{V|d{V}|dkSNz is-enabledstdoutstderrr)r1r*r create_subprocess_execsuDEVNULL communicatewait)r+rprocrcs r is_enabledz_SystemctlBased.is_enabled^st/AB3 BJ                  99;;      Qwrc|jd|jg}tj|tjtj}|dkSrH)r1r*rMcallrN)r+rrRs ris_enabled_syncz_SystemctlBased.is_enabled_syncgs7t/AB WSBJ ? ? ?Qwrc0|jdg|rdgng|jS)Ndisabler9r0r:s rrXz_SystemctlBased.disablels9    &wiiB      rc |jd|jgS)Nreloadr0r2s rrZz_SystemctlBased.reloadus (D,>??rcbK|jd|jg}t|d{V\}}}|dkS)Nz is-activer)r1r*r)r+r exit_coder$s rrz_SystemctlBased.is_activeys?d.@A #C...... 1aA~rc |jd|jgS)Nz reset-failedr0r2s rrz_SystemctlBased.reset_failed~s .$2DEErctj|jd|jgtjtj}|jdkS)NcatrIr)rMrr1r*rN returncode)r+cps r unit_existsz_SystemctlBased.unit_existssA V  ud&8 9::    }!!rN)__name__ __module__ __qualname__r1r-rr/r4rboolr;r8rSrVrXrZrrrbrrrrr2s]K***??Z?>>Z>AAZA $   Z !4!!!!"  d   Z @@Z@ FFZF"""""rceZdZdZdS)_CentOs7z/usr/bin/systemctlNrcrdrer1rrrrhrhs&KKKrrhceZdZdZdS) _DebianUbuntuz/bin/systemctlNrirrrrkrks"KKKrrkcttfD]3}tj|jr ||cS4t d)Nz'Cannot instantiate appropriate adaptor.)rkrhospathexistsr1 RuntimeError)r,as radaptorrrsVX &## 7>>!- ( ( #1\?? " " " # @ A AArcKt|}t|d}|d{Vr|d{Vs|d{Vt |fd{Vt dD]9}t jdd{V|d{VrdS:t d|d|dSdSdS)Nz.socketrzFailed to await active z.socket after reseting ) rrrSrrr%rr r!r error)r, agent_serviceagent_service_socketr$s ractivate_socket_servicerxsL))M"l#;#;#;<<#--//////// *4466666666 #//111111111!="2333333333q  A-"" " " " " " " ")3355555555     l             rc4ttjSr))rrrSVC_NAMErrrimunify360_servicer{s 4= ! !!rc ttS#t$rtdYdSwxYw)Nz5DOS Protector service is not available on this system)rrDOS_PROTECTOR_SERVICE_NAMErpr inforrr imunify360_dos_protector_servicersK1222  KLLLtts $>>c*ttSr))rrUAL_SERVICE_NAMErrrimunify360_ual_servicer # $ $$rc*ttSr))rrPAM_SERVICE_NAMErrrimunify360_pam_servicerrrc*ttSr))rrSCANLOGD_SERVICE_NAMErrrimunify360_scanlogd_servicers ( ) ))rc*ttSr))rrAGENT_SERVICE_NAMErrrimunify360_agent_servicers % & &&rctt}|rttStddS)Nz9Auditd-log-reader service is not available on this system)rrAUDITD_SERVICE_NAMErbr r~)units rimunify360_auditd_servicersK & ' 'D ,*+++ KKKLLL 4r)%r loggingrm subprocessrMtypingr defence360agent.contracts.configrdefence360agent.utilsrrrr getLoggerrcr r}rrrrrrr%rrhrkrrrxr{rrrrrrrrrrs 111111NNNNNNNNNNNN  8 $ $85#1-'#()####*V"V"V"V"V"V"V"V"r''''''''#####O###BBB   0"""%%%%%%***'''rdefence360agent/subsys/__pycache__/svcctl.cpython-311.pyc0000644000000000000000000002605200000000000020313 0ustar r_j4ddlZddlZddlZddlZddlmZddlmZddl m Z m Z m Z m Z ejeZdZdZdZdZd Zd Zd Zd ed fdZGdd ZGddeZGddeZdZdZdZdZdZ dZ!dZ"dZ#dZ$dS)N)Iterable)Core) check_run CheckRunErrorrun OsReleaseInfozimunify360-dos-protectionz imunify360-unified-access-loggerzimunify360-pamzimunify-auditd-log-readerzimunify360-scanlogdzimunify360-agentcfd}|S)Nc|K|i|}td|t|d{VdS)Nzcheck_call(%r))loggerdebugr)argskwargscmdfuncs R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/svcctl.pywrapperz_apply_cmd..wrappersTdD#F## %s+++nn)rrs` r _apply_cmdrs# Nrservices_SystemctlBasedcK|D]} |d{V|d{Vn5#t$r(}td||Yd}~dSd}~wwxYwt dD]T}|d{Vrn7td|dtjdd{VUdS)Nz/Failed to reset failed state for service %s: %s z4Service %s is still not active, sleep for %s seconds) reset_failedrestartrr warningrange is_activeasynciosleep)rse_s r_reset_failed_stater%s;## .."" " " " " " " "))++            NNA1a    FFFFFF   r # #A[[]]""""""  NNF1   -"" " " " " " " " "##s4= A/A**A/ceZdZdZdZedZedZedZede fdZ de fdZ d Z d Z ede fd Zed Zd ZedZdZdS)r systemctlc||_dSN) _service_name)self service_names r__init__z_SystemctlBased.__init__5s)rc |jd|jgS)Nstart SVC_CTL_BINr*r+s rr/z_SystemctlBased.start8s '4+=>>rc |jd|jgS)Nstopr0r2s rr4z_SystemctlBased.stop<s &$*<==rc |jd|jgS)Nrr0r2s rrz_SystemctlBased.restart@s )T-?@@rnowc0|jdg|rdgng|jS)Nenable--nowr0r+r6s r _enable_nowz_SystemctlBased._enable_nowDs9    &wiiB      rcjK||d{Vi} tj|n#ttf$rYdSwxYw|dddkrdS|dddkr|d{VdSdS)N)r6IDubuntu VERSION_IDz16.04)r;rdict_from_fileFileNotFoundErrorPermissionErrorgetlowerr)r+r6osinfos rr8z_SystemctlBased.enableMs3'''''''''    ( 0 0 0 0!?3    FF  ::dB   % % ' '8 3 3 F ::lB ' '7 2 2,,..  3 2s7A  A cK|jd|jg}tj|tjtjdd{V}|d{V|d{V}|dkSNz is-enabledstdoutstderrr)r1r*r create_subprocess_execsuDEVNULL communicatewait)r+rprocrcs r is_enabledz_SystemctlBased.is_enabled^st/AB3 BJ                  99;;      Qwrc|jd|jg}tj|tjtj}|dkSrH)r1r*rMcallrN)r+rrRs ris_enabled_syncz_SystemctlBased.is_enabled_syncgs7t/AB WSBJ ? ? ?Qwrc0|jdg|rdgng|jS)Ndisabler9r0r:s rrXz_SystemctlBased.disablels9    &wiiB      rc |jd|jgS)Nreloadr0r2s rrZz_SystemctlBased.reloadus (D,>??rcbK|jd|jg}t|d{V\}}}|dkS)Nz is-activer)r1r*r)r+r exit_coder$s rrz_SystemctlBased.is_activeys?d.@A #C...... 1aA~rc |jd|jgS)Nz reset-failedr0r2s rrz_SystemctlBased.reset_failed~s .$2DEErctj|jd|jgtjtj}|jdkS)NcatrIr)rMrr1r*rN returncode)r+cps r unit_existsz_SystemctlBased.unit_existssA V  ud&8 9::    }!!rN)__name__ __module__ __qualname__r1r-rr/r4rboolr;r8rSrVrXrZrrrbrrrrr2s]K***??Z?>>Z>AAZA $   Z !4!!!!"  d   Z @@Z@ FFZF"""""rceZdZdZdS)_CentOs7z/usr/bin/systemctlNrcrdrer1rrrrhrhs&KKKrrhceZdZdZdS) _DebianUbuntuz/bin/systemctlNrirrrrkrks"KKKrrkcttfD]3}tj|jr ||cS4t d)Nz'Cannot instantiate appropriate adaptor.)rkrhospathexistsr1 RuntimeError)r,as radaptorrrsVX &## 7>>!- ( ( #1\?? " " " # @ A AArcKt|}t|d}|d{Vr|d{Vs|d{Vt |fd{Vt dD]9}t jdd{V|d{VrdS:t d|d|dSdSdS)Nz.socketrzFailed to await active z.socket after reseting ) rrrSrrr%rr r!r error)r, agent_serviceagent_service_socketr$s ractivate_socket_servicerxsL))M"l#;#;#;<<#--//////// *4466666666 #//111111111!="2333333333q  A-"" " " " " " " ")3355555555     l             rc4ttjSr))rrrSVC_NAMErrrimunify360_servicer{s 4= ! !!rc ttS#t$rtdYdSwxYw)Nz5DOS Protector service is not available on this system)rrDOS_PROTECTOR_SERVICE_NAMErpr inforrr imunify360_dos_protector_servicersK1222  KLLLtts $>>c*ttSr))rrUAL_SERVICE_NAMErrrimunify360_ual_servicer # $ $$rc*ttSr))rrPAM_SERVICE_NAMErrrimunify360_pam_servicerrrc*ttSr))rrSCANLOGD_SERVICE_NAMErrrimunify360_scanlogd_servicers ( ) ))rc*ttSr))rrAGENT_SERVICE_NAMErrrimunify360_agent_servicers % & &&rctt}|rttStddS)Nz9Auditd-log-reader service is not available on this system)rrAUDITD_SERVICE_NAMErbr r~)units rimunify360_auditd_servicersK & ' 'D ,*+++ KKKLLL 4r)%r loggingrm subprocessrMtypingr defence360agent.contracts.configrdefence360agent.utilsrrrr getLoggerrcr r}rrrrrrr%rrhrkrrrxr{rrrrrrrrrrs 111111NNNNNNNNNNNN  8 $ $85#1-'#()####*V"V"V"V"V"V"V"V"r''''''''#####O###BBB   0"""%%%%%%***'''rdefence360agent/subsys/__pycache__/sysctl.cpython-311.opt-1.pyc0000644000000000000000000000315300000000000021272 0ustar r_j{ ddlZdZdZdZdS)Ncntjjtjddg|dRS)Nprocsys.)ospathjoinsepsplit)names R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/sysctl.py _build_pathrs, 7< @ 3 @ @ @@ctt|5}|}|jrt |cdddS|cdddS#1swxYwYdS)N)openrreadstripisdigitint)r fdatas r rrs k$ Avvxx~~ < t99  srsH AAArdefence360agent/subsys/__pycache__/sysctl.cpython-311.pyc0000644000000000000000000000315300000000000020333 0ustar r_j{ ddlZdZdZdZdS)Ncntjjtjddg|dRS)Nprocsys.)ospathjoinsepsplit)names R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/sysctl.py _build_pathrs, 7< @ 3 @ @ @@ctt|5}|}|jrt |cdddS|cdddS#1swxYwYdS)N)openrreadstripisdigitint)r fdatas r rrs k$ Avvxx~~ < t99  srsH AAArdefence360agent/subsys/__pycache__/systemd_notifier.cpython-311.opt-1.pyc0000644000000000000000000000531600000000000023343 0ustar r_j~dZddlZddlZddlZddlmZejeZda da Gdde Z dZ dZdS) z"Notify systemd about process stateN)ANTIVIRUS_MODEFc"eZdZdZdZdZdZdZdS) AgentStatez*Allowed agent state for notifying systemd.zREADY=1zSTATUS=Starting main processz#STATUS=Applying database migrationszSTATUS=DemonizedN)__name__ __module__ __qualname____doc__READYSTARTING MIGRATING DAEMONIZED\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/systemd_notifier.pyrrs(44 E-H5I#JJJrrcbts"tjddadatS)N NOTIFY_SOCKETT)_socket_detachedosenvironpop_notify_socket_addrrrr_take_notify_socketrs.   jnn_dCC rctrdSt}|sdS|dr d|ddzn|} tjtjtjtjz5}|||| ddddS#1swxYwYdS#t$r&}t d|Yd}~dSd}~wwxYw)z Send notification to systemd, allowed formats described here https://www.freedesktop.org/software/systemd/man/sd_notify.html For example: notify("STATUS=Almost ready") N@z9some problem has occurred during notifying of systemd: %s) rr startswithsocketAF_UNIX SOCK_DGRAM SOCK_CLOEXECconnectsendallencodeOSErrorlogger exception)stateaddr connect_addrsockes rnotifyr-#se  D '+ooc&:&:D4$qrr(??L  ] NF-0CC   )  LL & & & LL ( ( (  ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )     G           s<6C7=C4 CCCC C C>C99C>)r loggingrr defence360agent.contracts.configr getLoggerrr&rrobjectrrr-rrrr2s(( ;;;;;;  8 $ $$$$$$$$$     rdefence360agent/subsys/__pycache__/systemd_notifier.cpython-311.pyc0000644000000000000000000000531600000000000022404 0ustar r_j~dZddlZddlZddlZddlmZejeZda da Gdde Z dZ dZdS) z"Notify systemd about process stateN)ANTIVIRUS_MODEFc"eZdZdZdZdZdZdZdS) AgentStatez*Allowed agent state for notifying systemd.zREADY=1zSTATUS=Starting main processz#STATUS=Applying database migrationszSTATUS=DemonizedN)__name__ __module__ __qualname____doc__READYSTARTING MIGRATING DAEMONIZED\/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/systemd_notifier.pyrrs(44 E-H5I#JJJrrcbts"tjddadatS)N NOTIFY_SOCKETT)_socket_detachedosenvironpop_notify_socket_addrrrr_take_notify_socketrs.   jnn_dCC rctrdSt}|sdS|dr d|ddzn|} tjtjtjtjz5}|||| ddddS#1swxYwYdS#t$r&}t d|Yd}~dSd}~wwxYw)z Send notification to systemd, allowed formats described here https://www.freedesktop.org/software/systemd/man/sd_notify.html For example: notify("STATUS=Almost ready") N@z9some problem has occurred during notifying of systemd: %s) rr startswithsocketAF_UNIX SOCK_DGRAM SOCK_CLOEXECconnectsendallencodeOSErrorlogger exception)stateaddr connect_addrsockes rnotifyr-#se  D '+ooc&:&:D4$qrr(??L  ] NF-0CC   )  LL & & & LL ( ( (  ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) )     G           s<6C7=C4 CCCC C C>C99C>)r loggingrr defence360agent.contracts.configr getLoggerrr&rrobjectrrr-rrrr2s(( ;;;;;;  8 $ $$$$$$$$$     rdefence360agent/subsys/__pycache__/web_server.cpython-311.opt-1.pyc0000644000000000000000000012405000000000000022114 0ustar r_jk ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m cm Z ddlmZddlmZddlmZddlmZddlmZddlmZmZmZmZddlmZmZm Z m!Z!m"Z"m#Z#m$Z$ddl%Z%dd l&m'Z'dd l(m)Z)m*Z*dd l+m,Z,dd l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5dd l6m7Z7e8ej9:ddZ; dZ<edZ=dZ>dZ?dZ@dZAdZBdZCejDdZEeFdeGe jHDZIdZJejKeLZMGddeNZOGddeNZPGdd ZQd!ZRd"ZSd#eTfd$ZUd#e!eTfd%ZVd#eWfd&ZXeYd'd(ZZd)Z[d*Z\d+Z] dXd-egefd.e8fd/Z^d0Z_d#e eTfd1Z`d#e!e$eTfd2Zad3Zbejcd45d#edfd6Zed7edd#e eTfd8ZfdYd7edd#e$eTfd:Zgd#edfd;Zhd#e!eTfd<ZidZd7edd#e$eTfd>Zjd#e$eTfd?Zked@ZldAeTd#edfdBZmd[dCZne7joe;d[dDZpd[dEZqdFZrdGZsd#edfdHZtd#edfdIZud\dJZvd#edfdKZwdYdLZxdMZydNeWd#e eWfdOZzdPZ{dQZ|e.d45dRZ}e4ee8ej9:dSdTUVdWZ~dS)]N)suppress) ContextVar) timedelta)Version)Path)CalledProcessError check_call check_outputDEVNULL)AnyCallableListOptionalSetTupleIterable)IntegrationConfig)is_generic_panel_installedis_plesk_installed)g)async_lru_cacheatomic_rewrite check_runget_system_user_names OsReleaseInfo CheckRunError TimedCacheBACKUP_EXTENSION)webserver_gracefull_restart!IM360_GRACEFUL_RESTART_MIN_PERIODi,z*/usr/local/cpanel/scripts/restartsrv_httpdz/tmp/lshttpd/lshttpd.pid)/usr/local/lsws/bin/lswsctrl condrestart)r!restartz%/usr/local/lsws/conf/httpd_config.xmlz/usr/local/lsws/bin/litespeedz/usr/sbin/apache2z/usr/sbin/httpdz Server version:.*(\d+\.\d+\.\d+)c#>K|]}|VdSN)encode).0xs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/web_server.py r*:s*@@1AHHJJ@@@@@@apacheceZdZdZdS)NotRunningErrorz[ Error for cases when the web server is expected to be running but it is not. N__name__ __module__ __qualname____doc__r+r)r.r.@sr+r.ceZdZdZdS)ConfigInvalidErrorzO Error used to indicate that the web server config is having error(s). Nr/r4r+r)r6r6Hsr+r6ceZdZdZdZdZdZdZdZdZ dZ d Z d e fd Z d e fd Zd eeeeffdZdZd efdZdS)LiteSpeedConfiguseIpInProxyHeadersecurity accessControlallowdenyrc8tj||_dSr%)ET fromstringconfig)selfcontents r)__init__zLiteSpeedConfig.__init__XsmG,, r+returnc|j|j}||js|jSt |jSr%)rCfindCLIENT_IP_IN_HEADER_TAGtextCLIENT_IP_IN_HEADER_DISABLEDintrDelements r)client_ip_in_headerz#LiteSpeedConfig.client_ip_in_header[s>+""4#?@@ ?',?4 47<   r+valuec|j|j}|3tj|j}|j|t ||_dSr%)rCrIrJrAElementappendstrrK)rDrQrOs r)set_client_ip_in_headerz'LiteSpeedConfig.set_client_ip_in_headerasX+""4#?@@ ?j!=>>G K  w ' ' '5zz r+c|jdd|j|j|jg}|*|jr#d|jDStS)N/.ch|]R}|dD]:}||dr |ddn||df;SS),TN)splitendswith)r'sitems r) z>LiteSpeedConfig.access_control_allowed_list..ts}GGCLL "mmC00:crcdDMM#rr\r4)r'ras r) zCLiteSpeedConfig.set_access_control_allowed_list..}s1KKK4$q'6a3tAwKKKr+r[rXrY) rcrCrIrdrerfrArSrTrK)rDalloweditemsrQrOaccess_controlr:s r)set_access_control_allowed_listz/LiteSpeedConfig.set_access_control_allowed_list|sLKK7KKK+"" HH%+3       ?j!@AAG![--)/N%!#D,C!D!D;++D,=>>#!z$*;<.is_generic_panel_on_apaches. % ' ' P$(}EEO Our+exeNrz#Can't determine apache bin path: %s)rrrrAPACHE2_BIN_PATHHTTPD_BIN_PATHrgr_apache_running_process_infoospathsamefilerrr)rrr sys_usersrhttpd_process_exeexcs r)rrs#" -"66## : : < <#% " )++,,}|jVHdS)rN)z/httpdz/apache2usernamerr_)r'prs r)r*z/_apache_running_process_info..sp  u 1F5M223IJJ2F:.);;F<;;;  r+)namerruidsgidsattrsN)ranger IndexErrornextr process_iter)r_s` r)rr s 1XX j ! !      #0III                        s1A''A+ .A+ ctdh}|stdtj||dd|dddS)z&Make web server user/group own *path*.rootrz5Can't find running apache process without root owner.rrrN)rr.rchown)rrs r)rrs] "& : : :D   C   HT4<?DLO44444r+c`tdtjgdDdS)z;Return path to a running nginx binary or None if not found.c3K|]i}|jdZ|jddr:|jd-d|jdvr|jddvZ|jdVjdS)rNnginxrr)rzwww-datar)r'rs r)r*z%find_running_nginx..+s v*F6N++G44+F5M-qve},,F:&*??? F5M@??? r+)rrrrN)rrrr4r+r)find_running_nginxr(sJ  (/J/J/JKKK     r+ webserver_running_cb granularitycK|dksJt|D]/}|}|r|cStj||z d{V0|S)Nr)rasynciosleep)r timeout_secrrresults r)check_with_timeoutr:sz ???? ;  %%''  MMMmK+56666666666 r+c@tjdS)z8 though, available != running :return bool: z/etc/cpanel/ea4/is_ea4)rrisfiler4r+r)is_EA4_availablerJs 7>>2 3 33r+ctjt}|r|gStjtjzr#dddt j|gS|ddgS)a{ :return list: command which can be passed to check_call(..., shell=False) 'apache2 -k graceful' will not work for Ubuntu and will produce 'Invalid Mutex directory in argument file:${APACHE_LOCK_DIR}' error. https://serverfault.com/questions/558283/apache2-config-variable-is-not-defined That is why this specialization for Ubuntu graceful restart. systemctlreloadz--job-mode=replace-irreversiblyz-kgraceful) rrCPANEL_RESTART_APACHE_SCRIPTrrrrrbasename) apachectlrestartsrv_httpds r)_apache_graceful_restart_cmdrRsv|$@AA" !!!55 -   - G  Y ' '   4,,r+ctjr tjdd}|stddS|}t j|dr|Std|n*#t$rtdYnwxYwdS)Nrgraceful_restart_scriptz'graceful_restart_script option is emptyrz,Web server restart script does not exist: %sz;Integration config is missing graceful_restart_script field) rrto_dictrrr^rrKeyError)restart_scriptcmds r)+_graceful_restart_cmd_from_integration_confrls!! .688F)N" =t &&((Cw~~c!f%%  NN>        NNM      " 4sB))$CCr>)maxsizecBtjd}|sdS t|dgt}n#t t f$rYdSwxYwtjd|}|duo*t| dtkS)N systemd-runFz --version)stderrzsystemd\s+(\d+)r>) rrr r decoderrresearchrMgroup_SYSTEMD_RUN_WAIT_MIN_VERSION) systemd_runoutmatchs r)_systemd_run_supports_waitrs,}--K uK5gFFFMMOO ' (uu I(# . .E    EKKNN<<s*AAAwaitcg}tjdx}rC||dddgz }|r#tr|d|d|S)Nrz-pzSendSIGKILL=noz--slice=graceful_restartz--waitz--)rrrrT)rprefixrs r)_systemd_run_prefixrs|Fl=111{     &     $.00 $ MM( # # # d Mr+Fct|}t}||t|zStr|ttzSt x}r|t |zStd)z Gracefully restart a web server.NCould not detect a web server)rrlistrLITESPEED_RESTART_CMDrr RuntimeError)rrrrs r)_graceful_restart_cmdrs  & &F 5 7 7C S !!423333"$$$y@4Y???? 6 7 77r+cJtjtSr%)rrrLITESPEED_CONF_PATHr4r+r)_litespeed_installedrs 7>>- . ..r+cd}tr7 tjdd}n#t$rYdSwxYw|tkrdSd}t jt jzr4ts|r$tj tStj tS)usystemd unit for this host's Apache, or None when the host is not Apache-based. Derived from OS/panel, not a running process — recovery runs precisely when the server is not alive.FrrNT)rrrrrrrrrrrrrr)on_generic_apachers r)_apache_systemd_unitrs!##! +/ mLLKK   44  & 4 -"6622 12w 0111 7  N + ++s ( 66Tct|}tr|ttzSt jt x}r||dgzSt}|td|dd|gzS)a-Full (non-graceful) restart to bring a web server back up after a reload left it down. Detects the server by install/config presence (not a running process, which may be down) and raises when no safe command is known (e.g. generic nginx, which has only a graceful integration script). z --restartNz0No safe hard-restart command for this web serverrr#) rrrLITESPEED_HARD_RESTART_CMDrrrrr)rrrunits r)_hard_restart_cmdr s ! & &F978888!<(DEEE8);777  ! !D |MNNN [)T2 22r+ctr= tjdd}|r|Sn#t$rYnwxYwt x}r(t jt jzrddgS|dgStrtdgStx}r|dgStd)Nrconfig_test_scriptr configtest-tr) rrrr^rrrrrrrrr)r apache_bin nginx_bins r)_configtest_cmdrs!## #' 6JKKC #yy{{" #    D $%%%z!  " "]%9 9 /. .D!!   !  $''(** *!4  6 7 77s*< A A graceful_restart_caller new_configc Ktjtzfd tj}t ||sdSfd t dd{V t n=#t$r0}t d| Yd}~dSd}~wwxYwtj fd }tjt| t }t#jd }t&|j} | d{Vt&|n#t&|wxYwtd dS#t0$r/}t d | Yd}~nd}~wwxYwdS)a Update Web-server config with fallback in case of an error happens. It tries to do all the best but because of graceful_restart() the faulty config might still be applied but in practice it is barely probable (because of premature config check). 1. The new config is checked before to be applied. 2. The new config (if checked valid) is atomically applied. 3. The graceful Web-server restart is scheduled. It may hold the actual restart for some time, but it is a required workaround of a litespeed issue. 4. If the Web-server failed to restart the config is reverted. Return value: True if no errors (at least up to the server restart), False if There was an error and config was reverted. Note: It is possible that the config may be reverted even when return value is True. It is because the graceful_restart may delay the actual restart and config may be reverted on that (delayed) stage. ctt5tjddddS#1swxYwYdSr%)rFileNotFoundErrorrunlink)config_backup_pathsr) remove_backupz)safe_update_config..remove_backups ' ( ( * * I( ) ) ) * * * * * * * * * * * * * * * * * *s 8<<)backupTc tjdS#t$r&tdYdSwxYw)Nw)rrenameropenclose)r config_pathsr)revertz"safe_update_config..revert"sa + I(+ 6 6 6 6 6  + + + c " " ( ( * * * * * * +s,A  A raise_exceptionNz*Failed to get graceful restart command: %sFcd}d}|s|td|t d}||t}||dSdS)Nc|sD|2td|dSdSdS)Nz'The reverted config seems to be invalidexc_info cancelled exceptionrcriticalfuts r)log_config_errorzFsafe_update_config..restart_callback..log_config_error9sb}}3==??+FOOA!$$+F+Fr+c|sD|2td|dSdSdS)Nzuncaught exceptionr'r)r-s r)log_uncaught_exceptionzLsafe_update_config..restart_callback..log_uncaught_exception@sa}}3==??+FOO,s}}$+F+Fr+z7Web server failed to start... Revert changes back. (%s)Tr#)r*r+rerror create_taskradd_done_callback_graceful_restart)taskr/r1loopr restart_cmdr"s r)restart_callbackz,safe_update_config..restart_callback8s       >>## (8(8(D MNN$$'' 4(H(H(HII&&'7888''(9+(F(FGG&&'=>>>>> r+) done_callbackr>z)Successfully scheduled web server restartz Web server config is invalid: %s)rfspathrrrrrrrrr2rget_running_looprcoalesce_callsGRACEFUL_RESTART_MIN_PERIODr5inspectstack_graceful_restart_callerrgfunctionresetrr6) r!r make_backuper9graceful_restart caller_frame context_tokenrr7rr8r"s ` @@@@@r)safe_update_configrIsh*;//2BB*****'..--K +z+ F F Ft++++++6..........  /11KK    LLEq I I I FHHH55555  '))        8 6E '7G   }q) 044\5JKK  :"";// / / / / / / / $ * *= 9 9 9 9 $ * *= 9 9 9 9 ?@@@ti  7;;;j 5s<$F+;B C%B??CE00F + G$5%GG$cKt t|p td{VtddS#t $r&}td|Yd}~dSd}~wwxYw)] Gracefully restart a web server. If web server cannot be detected, do nothing. N!Successfully restarted web server"Could not restart a Web server: %s)_log_graceful_restart_startrrrrrr)r8errs r)r5r5cs  !!!9 >'<'>'>?????????  788888 BBB;SAAAAAAAAABs#A BA<<BcKt|}|t_ |d{V tjdS#tjdwxYw)Nweb_server_restart_task)r5rrQpop)r8r6s r)_graceful_restart_coalescedrSrs] [ ) )D $A)zzzzzz '(((('((((s <AcKtjd}t|j} t |d{V}t|n#t|wxYw|S)rKr>N)r?r@rArgrBrSrC)r8rGrHrs r)rFrF|s=??1%L,001FGGM62;???????? &&}5555 &&}5555 Ms A,,Bcptd}td|dS)Nunknownz/Performing web server graceful restart, from %s)rArrr)callers r)rNrNs0 % ) )) 4 4F KKA6JJJJJr+ctjd}t|j} t t|n#t|wxYw ttttt ddS#t$r&}t d|Yd}~dSd}~wwxYw)zk Gracefully restart a web server synchronously. If web server cannot be detected, do nothing. r>)rrrLrMN)r?r@rArgrBrNrCr rr rrrr)rGrHrOs r)graceful_restart_syncrYs =??1%L,001FGGM6#%%% &&}5555 &&}55559(**77KKKK  788888 BBB;SAAAAAAAAABs#A##A?(C C7C22C7cKtjd}t|j} t t|n#t|wxYw td}n3#t$r&}t d|Yd}~dSd}~wwxYwt|d{Vrt ddSt dtd{Vt|d{Vrt d dStd{VS) ayGraceful web-server restart that confirms the reload actually completed and recovers the server if it did not. Unlike graceful_restart() it bypasses the coalesce throttle (the post-update reload must never be dropped); unlike graceful_restart_sync() it observes the reload outcome instead of returning as soon as systemd-run queues the transient unit. r>TrrMNFrLzLWeb server reload after update did not complete cleanly; attempting recoveryz-Web server recovered on graceful reload retry)r?r@rArgrBrNrCrrrr_reload_confirmedrr2_log_failed_configtest _hard_restart)rGrHrrOs r)graceful_restart_confirmedr_s=??1%L,001FGGM6#%%% &&}5555 &&}5555#... ;SAAAuuuuus # ####### 7888t LL  ! " """""""" s # ####### CDDDt  s#A%%BB C CCcK t|d{Vn:#ttf$r&}td|Yd}~dSd}~wwxYwt rdS t dd{Vn#t$rYdSwxYwdS)aRun *cmd* and report whether the reload truly succeeded. With systemd-run --wait the exit code already reflects completion; on older systemd (no --wait) the reload is fire-and-forget, so fall back to a config test to detect a broken reload. Nz'Web server reload returned an error: %sFTr#)rrrrrrrr6rrOs r)r\r\snn < (@#FFFuuuuu"##t........... uu 4s&AA  A%A<< B  B cK tdd{VdS#t$r&}td|Yd}~dSd}~wwxYw)NTr#z.Web server config test failed after update: %s)rr6rr2)rOs r)r]r]sL............ LLL EsKKKKKKKKKLs A AA cjK td}n3#t$r&}td|Yd}~dSd}~wwxYw t |d{Vn:#t tf$r&}td|Yd}~dSd}~wwxYwtddS)NTr[zCannot recover web server: %sFz"Web server hard restart failed: %sz-Web server hard-restarted after failed reload)r rrr2rrrras r)r^r^sT***  4c:::uuuuunn < ( 93???uuuuu KK?@@@ 4s, AAA AB0BBcKtd tttd{VdS#t $r8}td||rt d|Yd}~dSd}~wwxYw)z\ Check web server's config file. If web server cannot be detected, do nothing. z!Performing web server config test) raise_excNzCould not run configtest: %szFailed to check config)rrrrr6rr)r$rOs r)rrs  KK3444H))5GHHHHHHHHHHHH HHH5s;;;  H$%=>>C G H H H H H HHs(A B -BB ct|}|"t|dSt d|)Nr>z)Failed to parse apache version string: {})apache_version_regexprrrrr)outputrs r)_parse_apache_version_outputri sV ! ( ( 0 0E u{{1~~&&& 7 > >v F F   r+rhc>d|DS)a: Parse response of httpd -M :param output: stdout of httpd -M (with spaces before module name) Output example: Loaded Modules: core_module (static) so_module (static) http_module (static) mpm_prefork_module (shared) :return: list with installed modules cg|]H}|t|dIS)r) startswith BYTE_SPACESstripr^)r'lines r)rkz-_parse_apache_module_list..!sS     ??; ' '  Q   r+) splitlines)rhs r)_parse_apache_module_listrqs/  %%''   r+cg}|dD]L}|d}|dkr/|||dM|S)N rXr)rr^rIrTrn)dumpincludesroindexs r)_parse_includesrw(spH ##D))22 # 199 OODL..00 1 1 1 Or+ctK ttgdd{VS#t$rgcYSwxYw)N)rrz-D DUMP_INCLUDES)rwrrr4r+r) dump_includesrz1sgFFFGG G G G G G G     s #( 77cKt}|tdt|dgd{V}t|}t d||S)Nrz-vzApache %s version detected)rr.rrirrr)rrversions r)apache_versionr}:sy!!J5666:t,-- - - - - - -C*3::<<88G KK,g666 Nr+'IMUNIFY360_APACHE_MODULES_CACHE_TIMEOUTiX)seconds) expirationcNKtdd{V}t|S)Nz-M)rrq)rs r)apache_modulesrEs5&d++ + + + + + +F $V , ,,r+)rr)F)Tr%)rGN)r functoolsr?rsloggingrrrrstringxml.etree.ElementTreeetreerurA contextlibr contextvarsrdatetimerpackaging.versionrpathlibr subprocessrr r r typingr r rrrrrr$defence360agent.api.integration_confr3defence360agent.application.determine_hosting_panelrr&defence360agent.internals.global_scoperdefence360agent.utilsrrrrrrrrdefence360agent.utils.commonrrMenvironrr>rrrr rrrrcompilergtupler whitespacermr getLoggerr0rrr.r6r8rrrUrrrr frozensetrrrrrrrrr lru_cacher~rrrrrr rrArIr5r=rSrFrNrYr_r\r]r^rrirqrwrzr}rr4r+r)rs  """""""""""""""%%%%%%LLLLLLLLLLLLFFFFFFFFFFFFFFFFFF BBBBBB544444                    EDDDDD!cJNN6?? L$9::GI=4&"" #FGGe@@V->(?(?@@@@@   8 $ $l TTTTTTTTn999   ;;;;; / ////u2.7Y[['''''T(555(  "2s7+      444-tCy----4Xhsm5L>!$Q D     dtCy"888#8888"/d////,hsm,,,,(33D3HSM3333(8#8888(&:&?@@^c^d^^^^B 9 9 9 9,+,GHH)))IH)     KKK 999*$!$$!$!$!$!ND.LLLL T     H H H H   eU & y JNNDc J J  -----r+defence360agent/subsys/__pycache__/web_server.cpython-311.pyc0000644000000000000000000012405000000000000021155 0ustar r_jk ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl m cm Z ddlmZddlmZddlmZddlmZddlmZddlmZmZmZmZddlmZmZm Z m!Z!m"Z"m#Z#m$Z$ddl%Z%dd l&m'Z'dd l(m)Z)m*Z*dd l+m,Z,dd l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5dd l6m7Z7e8ej9:ddZ; dZ<edZ=dZ>dZ?dZ@dZAdZBdZCejDdZEeFdeGe jHDZIdZJejKeLZMGddeNZOGddeNZPGdd ZQd!ZRd"ZSd#eTfd$ZUd#e!eTfd%ZVd#eWfd&ZXeYd'd(ZZd)Z[d*Z\d+Z] dXd-egefd.e8fd/Z^d0Z_d#e eTfd1Z`d#e!e$eTfd2Zad3Zbejcd45d#edfd6Zed7edd#e eTfd8ZfdYd7edd#e$eTfd:Zgd#edfd;Zhd#e!eTfd<ZidZd7edd#e$eTfd>Zjd#e$eTfd?Zked@ZldAeTd#edfdBZmd[dCZne7joe;d[dDZpd[dEZqdFZrdGZsd#edfdHZtd#edfdIZud\dJZvd#edfdKZwdYdLZxdMZydNeWd#e eWfdOZzdPZ{dQZ|e.d45dRZ}e4ee8ej9:dSdTUVdWZ~dS)]N)suppress) ContextVar) timedelta)Version)Path)CalledProcessError check_call check_outputDEVNULL)AnyCallableListOptionalSetTupleIterable)IntegrationConfig)is_generic_panel_installedis_plesk_installed)g)async_lru_cacheatomic_rewrite check_runget_system_user_names OsReleaseInfo CheckRunError TimedCacheBACKUP_EXTENSION)webserver_gracefull_restart!IM360_GRACEFUL_RESTART_MIN_PERIODi,z*/usr/local/cpanel/scripts/restartsrv_httpdz/tmp/lshttpd/lshttpd.pid)/usr/local/lsws/bin/lswsctrl condrestart)r!restartz%/usr/local/lsws/conf/httpd_config.xmlz/usr/local/lsws/bin/litespeedz/usr/sbin/apache2z/usr/sbin/httpdz Server version:.*(\d+\.\d+\.\d+)c#>K|]}|VdSN)encode).0xs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/web_server.py r*:s*@@1AHHJJ@@@@@@apacheceZdZdZdS)NotRunningErrorz[ Error for cases when the web server is expected to be running but it is not. N__name__ __module__ __qualname____doc__r+r)r.r.@sr+r.ceZdZdZdS)ConfigInvalidErrorzO Error used to indicate that the web server config is having error(s). Nr/r4r+r)r6r6Hsr+r6ceZdZdZdZdZdZdZdZdZ dZ d Z d e fd Z d e fd Zd eeeeffdZdZd efdZdS)LiteSpeedConfiguseIpInProxyHeadersecurity accessControlallowdenyrc8tj||_dSr%)ET fromstringconfig)selfcontents r)__init__zLiteSpeedConfig.__init__XsmG,, r+returnc|j|j}||js|jSt |jSr%)rCfindCLIENT_IP_IN_HEADER_TAGtextCLIENT_IP_IN_HEADER_DISABLEDintrDelements r)client_ip_in_headerz#LiteSpeedConfig.client_ip_in_header[s>+""4#?@@ ?',?4 47<   r+valuec|j|j}|3tj|j}|j|t ||_dSr%)rCrIrJrAElementappendstrrK)rDrQrOs r)set_client_ip_in_headerz'LiteSpeedConfig.set_client_ip_in_headerasX+""4#?@@ ?j!=>>G K  w ' ' '5zz r+c|jdd|j|j|jg}|*|jr#d|jDStS)N/.ch|]R}|dD]:}||dr |ddn||df;SS),TN)splitendswith)r'sitems r) z>LiteSpeedConfig.access_control_allowed_list..ts}GGCLL "mmC00:crcdDMM#rr\r4)r'ras r) zCLiteSpeedConfig.set_access_control_allowed_list..}s1KKK4$q'6a3tAwKKKr+r[rXrY) rcrCrIrdrerfrArSrTrK)rDalloweditemsrQrOaccess_controlr:s r)set_access_control_allowed_listz/LiteSpeedConfig.set_access_control_allowed_list|sLKK7KKK+"" HH%+3       ?j!@AAG![--)/N%!#D,C!D!D;++D,=>>#!z$*;<.is_generic_panel_on_apaches. % ' ' P$(}EEO Our+exeNrz#Can't determine apache bin path: %s)rrrrAPACHE2_BIN_PATHHTTPD_BIN_PATHrgr_apache_running_process_infoospathsamefilerrr)rrr sys_usersrhttpd_process_exeexcs r)rrs#" -"66## : : < <#% " )++,,}|jVHdS)rN)z/httpdz/apache2usernamerr_)r'prs r)r*z/_apache_running_process_info..sp  u 1F5M223IJJ2F:.);;F<;;;  r+)namerruidsgidsattrsN)ranger IndexErrornextr process_iter)r_s` r)rr s 1XX j ! !      #0III                        s1A''A+ .A+ ctdh}|stdtj||dd|dddS)z&Make web server user/group own *path*.rootrz5Can't find running apache process without root owner.rrrN)rr.rchown)rrs r)rrs] "& : : :D   C   HT4<?DLO44444r+c`tdtjgdDdS)z;Return path to a running nginx binary or None if not found.c3K|]i}|jdZ|jddr:|jd-d|jdvr|jddvZ|jdVjdS)rNnginxrr)rzwww-datar)r'rs r)r*z%find_running_nginx..+s v*F6N++G44+F5M-qve},,F:&*??? F5M@??? r+)rrrrN)rrrr4r+r)find_running_nginxr(sJ  (/J/J/JKKK     r+ webserver_running_cb granularitycK|dksJt|D]/}|}|r|cStj||z d{V0|S)Nr)rasynciosleep)r timeout_secrrresults r)check_with_timeoutr:sz ???? ;  %%''  MMMmK+56666666666 r+c@tjdS)z8 though, available != running :return bool: z/etc/cpanel/ea4/is_ea4)rrisfiler4r+r)is_EA4_availablerJs 7>>2 3 33r+ctjt}|r|gStjtjzr#dddt j|gS|ddgS)a{ :return list: command which can be passed to check_call(..., shell=False) 'apache2 -k graceful' will not work for Ubuntu and will produce 'Invalid Mutex directory in argument file:${APACHE_LOCK_DIR}' error. https://serverfault.com/questions/558283/apache2-config-variable-is-not-defined That is why this specialization for Ubuntu graceful restart. systemctlreloadz--job-mode=replace-irreversiblyz-kgraceful) rrCPANEL_RESTART_APACHE_SCRIPTrrrrrbasename) apachectlrestartsrv_httpds r)_apache_graceful_restart_cmdrRsv|$@AA" !!!55 -   - G  Y ' '   4,,r+ctjr tjdd}|stddS|}t j|dr|Std|n*#t$rtdYnwxYwdS)Nrgraceful_restart_scriptz'graceful_restart_script option is emptyrz,Web server restart script does not exist: %sz;Integration config is missing graceful_restart_script field) rrto_dictrrr^rrKeyError)restart_scriptcmds r)+_graceful_restart_cmd_from_integration_confrls!! .688F)N" =t &&((Cw~~c!f%%  NN>        NNM      " 4sB))$CCr>)maxsizecBtjd}|sdS t|dgt}n#t t f$rYdSwxYwtjd|}|duo*t| dtkS)N systemd-runFz --version)stderrzsystemd\s+(\d+)r>) rrr r decoderrresearchrMgroup_SYSTEMD_RUN_WAIT_MIN_VERSION) systemd_runoutmatchs r)_systemd_run_supports_waitrs,}--K uK5gFFFMMOO ' (uu I(# . .E    EKKNN<<s*AAAwaitcg}tjdx}rC||dddgz }|r#tr|d|d|S)Nrz-pzSendSIGKILL=noz--slice=graceful_restartz--waitz--)rrrrT)rprefixrs r)_systemd_run_prefixrs|Fl=111{     &     $.00 $ MM( # # # d Mr+Fct|}t}||t|zStr|ttzSt x}r|t |zStd)z Gracefully restart a web server.NCould not detect a web server)rrlistrLITESPEED_RESTART_CMDrr RuntimeError)rrrrs r)_graceful_restart_cmdrs  & &F 5 7 7C S !!423333"$$$y@4Y???? 6 7 77r+cJtjtSr%)rrrLITESPEED_CONF_PATHr4r+r)_litespeed_installedrs 7>>- . ..r+cd}tr7 tjdd}n#t$rYdSwxYw|tkrdSd}t jt jzr4ts|r$tj tStj tS)usystemd unit for this host's Apache, or None when the host is not Apache-based. Derived from OS/panel, not a running process — recovery runs precisely when the server is not alive.FrrNT)rrrrrrrrrrrrrr)on_generic_apachers r)_apache_systemd_unitrs!##! +/ mLLKK   44  & 4 -"6622 12w 0111 7  N + ++s ( 66Tct|}tr|ttzSt jt x}r||dgzSt}|td|dd|gzS)a-Full (non-graceful) restart to bring a web server back up after a reload left it down. Detects the server by install/config presence (not a running process, which may be down) and raises when no safe command is known (e.g. generic nginx, which has only a graceful integration script). z --restartNz0No safe hard-restart command for this web serverrr#) rrrLITESPEED_HARD_RESTART_CMDrrrrr)rrrunits r)_hard_restart_cmdr s ! & &F978888!<(DEEE8);777  ! !D |MNNN [)T2 22r+ctr= tjdd}|r|Sn#t$rYnwxYwt x}r(t jt jzrddgS|dgStrtdgStx}r|dgStd)Nrconfig_test_scriptr configtest-tr) rrrr^rrrrrrrrr)r apache_bin nginx_bins r)_configtest_cmdrs!## #' 6JKKC #yy{{" #    D $%%%z!  " "]%9 9 /. .D!!   !  $''(** *!4  6 7 77s*< A A graceful_restart_caller new_configc Ktjtzfd tj}t ||sdSfd t dd{V t n=#t$r0}t d| Yd}~dSd}~wwxYwtj fd }tjt| t }t#jd }t&|j} | d{Vt&|n#t&|wxYwtd dS#t0$r/}t d | Yd}~nd}~wwxYwdS)a Update Web-server config with fallback in case of an error happens. It tries to do all the best but because of graceful_restart() the faulty config might still be applied but in practice it is barely probable (because of premature config check). 1. The new config is checked before to be applied. 2. The new config (if checked valid) is atomically applied. 3. The graceful Web-server restart is scheduled. It may hold the actual restart for some time, but it is a required workaround of a litespeed issue. 4. If the Web-server failed to restart the config is reverted. Return value: True if no errors (at least up to the server restart), False if There was an error and config was reverted. Note: It is possible that the config may be reverted even when return value is True. It is because the graceful_restart may delay the actual restart and config may be reverted on that (delayed) stage. ctt5tjddddS#1swxYwYdSr%)rFileNotFoundErrorrunlink)config_backup_pathsr) remove_backupz)safe_update_config..remove_backups ' ( ( * * I( ) ) ) * * * * * * * * * * * * * * * * * *s 8<<)backupTc tjdS#t$r&tdYdSwxYw)Nw)rrenameropenclose)r config_pathsr)revertz"safe_update_config..revert"sa + I(+ 6 6 6 6 6  + + + c " " ( ( * * * * * * +s,A  A raise_exceptionNz*Failed to get graceful restart command: %sFcd}d}|s|td|t d}||t}||dSdS)Nc|sD|2td|dSdSdS)Nz'The reverted config seems to be invalidexc_info cancelled exceptionrcriticalfuts r)log_config_errorzFsafe_update_config..restart_callback..log_config_error9sb}}3==??+FOOA!$$+F+Fr+c|sD|2td|dSdSdS)Nzuncaught exceptionr'r)r-s r)log_uncaught_exceptionzLsafe_update_config..restart_callback..log_uncaught_exception@sa}}3==??+FOO,s}}$+F+Fr+z7Web server failed to start... Revert changes back. (%s)Tr#)r*r+rerror create_taskradd_done_callback_graceful_restart)taskr/r1loopr restart_cmdr"s r)restart_callbackz,safe_update_config..restart_callback8s       >>## (8(8(D MNN$$'' 4(H(H(HII&&'7888''(9+(F(FGG&&'=>>>>> r+) done_callbackr>z)Successfully scheduled web server restartz Web server config is invalid: %s)rfspathrrrrrrrrr2rget_running_looprcoalesce_callsGRACEFUL_RESTART_MIN_PERIODr5inspectstack_graceful_restart_callerrgfunctionresetrr6) r!r make_backuper9graceful_restart caller_frame context_tokenrr7rr8r"s ` @@@@@r)safe_update_configrIsh*;//2BB*****'..--K +z+ F F Ft++++++6..........  /11KK    LLEq I I I FHHH55555  '))        8 6E '7G   }q) 044\5JKK  :"";// / / / / / / / $ * *= 9 9 9 9 $ * *= 9 9 9 9 ?@@@ti  7;;;j 5s<$F+;B C%B??CE00F + G$5%GG$cKt t|p td{VtddS#t $r&}td|Yd}~dSd}~wwxYw)] Gracefully restart a web server. If web server cannot be detected, do nothing. N!Successfully restarted web server"Could not restart a Web server: %s)_log_graceful_restart_startrrrrrr)r8errs r)r5r5cs  !!!9 >'<'>'>?????????  788888 BBB;SAAAAAAAAABs#A BA<<BcKt|}|t_ |d{V tjdS#tjdwxYw)Nweb_server_restart_task)r5rrQpop)r8r6s r)_graceful_restart_coalescedrSrs] [ ) )D $A)zzzzzz '(((('((((s <AcKtjd}t|j} t |d{V}t|n#t|wxYw|S)rKr>N)r?r@rArgrBrSrC)r8rGrHrs r)rFrF|s=??1%L,001FGGM62;???????? &&}5555 &&}5555 Ms A,,Bcptd}td|dS)Nunknownz/Performing web server graceful restart, from %s)rArrr)callers r)rNrNs0 % ) )) 4 4F KKA6JJJJJr+ctjd}t|j} t t|n#t|wxYw ttttt ddS#t$r&}t d|Yd}~dSd}~wwxYw)zk Gracefully restart a web server synchronously. If web server cannot be detected, do nothing. r>)rrrLrMN)r?r@rArgrBrNrCr rr rrrr)rGrHrOs r)graceful_restart_syncrYs =??1%L,001FGGM6#%%% &&}5555 &&}55559(**77KKKK  788888 BBB;SAAAAAAAAABs#A##A?(C C7C22C7cKtjd}t|j} t t|n#t|wxYw td}n3#t$r&}t d|Yd}~dSd}~wwxYwt|d{Vrt ddSt dtd{Vt|d{Vrt d dStd{VS) ayGraceful web-server restart that confirms the reload actually completed and recovers the server if it did not. Unlike graceful_restart() it bypasses the coalesce throttle (the post-update reload must never be dropped); unlike graceful_restart_sync() it observes the reload outcome instead of returning as soon as systemd-run queues the transient unit. r>TrrMNFrLzLWeb server reload after update did not complete cleanly; attempting recoveryz-Web server recovered on graceful reload retry)r?r@rArgrBrNrCrrrr_reload_confirmedrr2_log_failed_configtest _hard_restart)rGrHrrOs r)graceful_restart_confirmedr_s=??1%L,001FGGM6#%%% &&}5555 &&}5555#... ;SAAAuuuuus # ####### 7888t LL  ! " """""""" s # ####### CDDDt  s#A%%BB C CCcK t|d{Vn:#ttf$r&}td|Yd}~dSd}~wwxYwt rdS t dd{Vn#t$rYdSwxYwdS)aRun *cmd* and report whether the reload truly succeeded. With systemd-run --wait the exit code already reflects completion; on older systemd (no --wait) the reload is fire-and-forget, so fall back to a config test to detect a broken reload. Nz'Web server reload returned an error: %sFTr#)rrrrrrrr6rrOs r)r\r\snn < (@#FFFuuuuu"##t........... uu 4s&AA  A%A<< B  B cK tdd{VdS#t$r&}td|Yd}~dSd}~wwxYw)NTr#z.Web server config test failed after update: %s)rr6rr2)rOs r)r]r]sL............ LLL EsKKKKKKKKKLs A AA cjK td}n3#t$r&}td|Yd}~dSd}~wwxYw t |d{Vn:#t tf$r&}td|Yd}~dSd}~wwxYwtddS)NTr[zCannot recover web server: %sFz"Web server hard restart failed: %sz-Web server hard-restarted after failed reload)r rrr2rrrras r)r^r^sT***  4c:::uuuuunn < ( 93???uuuuu KK?@@@ 4s, AAA AB0BBcKtd tttd{VdS#t $r8}td||rt d|Yd}~dSd}~wwxYw)z\ Check web server's config file. If web server cannot be detected, do nothing. z!Performing web server config test) raise_excNzCould not run configtest: %szFailed to check config)rrrrr6rr)r$rOs r)rrs  KK3444H))5GHHHHHHHHHHHH HHH5s;;;  H$%=>>C G H H H H H HHs(A B -BB ct|}|"t|dSt d|)Nr>z)Failed to parse apache version string: {})apache_version_regexprrrrr)outputrs r)_parse_apache_version_outputri sV ! ( ( 0 0E u{{1~~&&& 7 > >v F F   r+rhc>d|DS)a: Parse response of httpd -M :param output: stdout of httpd -M (with spaces before module name) Output example: Loaded Modules: core_module (static) so_module (static) http_module (static) mpm_prefork_module (shared) :return: list with installed modules cg|]H}|t|dIS)r) startswith BYTE_SPACESstripr^)r'lines r)rkz-_parse_apache_module_list..!sS     ??; ' '  Q   r+) splitlines)rhs r)_parse_apache_module_listrqs/  %%''   r+cg}|dD]L}|d}|dkr/|||dM|S)N rXr)rr^rIrTrn)dumpincludesroindexs r)_parse_includesrw(spH ##D))22 # 199 OODL..00 1 1 1 Or+ctK ttgdd{VS#t$rgcYSwxYw)N)rrz-D DUMP_INCLUDES)rwrrr4r+r) dump_includesrz1sgFFFGG G G G G G G     s #( 77cKt}|tdt|dgd{V}t|}t d||S)Nrz-vzApache %s version detected)rr.rrirrr)rrversions r)apache_versionr}:sy!!J5666:t,-- - - - - - -C*3::<<88G KK,g666 Nr+'IMUNIFY360_APACHE_MODULES_CACHE_TIMEOUTiX)seconds) expirationcNKtdd{V}t|S)Nz-M)rrq)rs r)apache_modulesrEs5&d++ + + + + + +F $V , ,,r+)rr)F)Tr%)rGN)r functoolsr?rsloggingrrrrstringxml.etree.ElementTreeetreerurA contextlibr contextvarsrdatetimerpackaging.versionrpathlibr subprocessrr r r typingr r rrrrrr$defence360agent.api.integration_confr3defence360agent.application.determine_hosting_panelrr&defence360agent.internals.global_scoperdefence360agent.utilsrrrrrrrrdefence360agent.utils.commonrrMenvironrr>rrrr rrrrcompilergtupler whitespacermr getLoggerr0rrr.r6r8rrrUrrrr frozensetrrrrrrrrr lru_cacher~rrrrrr rrArIr5r=rSrFrNrYr_r\r]r^rrirqrwrzr}rr4r+r)rs  """""""""""""""%%%%%%LLLLLLLLLLLLFFFFFFFFFFFFFFFFFF BBBBBB544444                    EDDDDD!cJNN6?? L$9::GI=4&"" #FGGe@@V->(?(?@@@@@   8 $ $l TTTTTTTTn999   ;;;;; / ////u2.7Y[['''''T(555(  "2s7+      444-tCy----4Xhsm5L>!$Q D     dtCy"888#8888"/d////,hsm,,,,(33D3HSM3333(8#8888(&:&?@@^c^d^^^^B 9 9 9 9,+,GHH)))IH)     KKK 999*$!$$!$!$!$!ND.LLLL T     H H H H   eU & y JNNDc J J  -----r+defence360agent/subsys/ainotify.py0000644000000000000000000001751400000000000014302 0ustar from collections import namedtuple import asyncio import ctypes import errno import logging import os import struct import platform from defence360agent.subsys import sysctl Event = namedtuple("Event", ("path", "flags", "cookie", "name", "wd")) logger = logging.getLogger(__name__) class Inotify: """ Tiny wrapper for inotify api. See `man inotify` for details """ ACCESS = 0x1 #: File was accessed MODIFY = 0x2 #: File was modified ATTRIB = 0x4 #: Metadata changed CLOSE_WRITE = 0x8 #: Writable file was closed CLOSE_NOWRITE = 0x10 #: Unwritable file closed OPEN = 0x20 #: File was opened MOVED_FROM = 0x40 #: File was moved from X MOVED_TO = 0x80 #: File was moved to Y CREATE = 0x100 #: Subfile was created DELETE = 0x200 #: Subfile was deleted DELETE_SELF = 0x400 #: Self was deleted MOVE_SELF = 0x800 #: Self was moved UNMOUNT = 0x2000 #: Backing fs was unmounted Q_OVERFLOW = 0x4000 #: Event queue overflowed IGNORED = 0x8000 #: File was ignored ONLYDIR = 0x1000000 #: only watch the path if it is a directory DONT_FOLLOW = 0x2000000 #: don't follow a sym link EXCL_UNLINK = 0x4000000 #: exclude events on unlinked objects MASK_ADD = 0x20000000 #: add to the mask of an already existing watch ISDIR = 0x40000000 #: event occurred against dir ONESHOT = 0x80000000 #: only send event once _n = "libc.{}".format("so.6" if platform.system() != "Darwin" else "dylib") _libc = ctypes.CDLL(_n, use_errno=True) event_prefix = struct.Struct("iIII") @staticmethod def _call(method, *args): """ Wrapper to all calls to C functions. Raises OSError with appropriate errno as argument in case of error return value. :param method: method to call :param args: method args :return: called function return value in case of success """ ret = getattr(Inotify._libc, method)(*args) if ret == -1: errno = ctypes.get_errno() raise OSError(errno, os.strerror(errno)) return ret @staticmethod def init(): """ Initialize an inotify instance. See `man inotify_init` for details :return: a file descriptor of new inotify instance """ return Inotify._call("inotify_init") @staticmethod def add_watch(fd, path, mask): """ Add a watch to an initialized inotify instance. This method is idempotent. If called twice with the same :fd: and :path: and different mask, will change watch flags of current watch. See `man inotify_add_watch` for details :param fd: file descriptor returned by `init()` :param path: path to file or directory to watch :param mask: bitmask of events to monitor :return: file descriptor of watch """ return Inotify._call("inotify_add_watch", fd, path, mask) @staticmethod def rm_watch(fd, wd): """ Remove existing watch from inotify instance. :param fd: file descriptor of inotify instance :param wd: watch file descriptor, returned by `add_watch()` :return: zero """ return Inotify._call("inotify_rm_watch", fd, wd) @staticmethod def unpack_prefix(data): """ Unpacks prefix of event struct. See `man inotify` for details :param data: struct bytestring :return: tuple of (wd, flag, cookie, length) """ return Inotify.event_prefix.unpack(data) @staticmethod def unpack_name(data): """ Unpack name field of inotify event struct See `man inotify` for details :param data: struct bytestring :return: name string """ return struct.unpack("%ds" % len(data), data)[0].rstrip(b"\x00") class Watcher: """ Asynchronous watcher for inotify events """ _CHUNK_SIZE = 1024 _MAX_WATCH_RETRIES = 3 _WATCHERS_RAISE_COEFF = 1.5 _MAX_USER_WATCHES = "fs.inotify.max_user_watches" def __init__(self, loop, coro_callback=None): self._loop = loop self._fd = Inotify.init() self._queue = asyncio.Queue() self._callback = coro_callback or self._queue.put self._loop.add_reader(self._fd, self._read) self._reset_state() def _reset_state(self): self.paths = {} self.descriptors = {} self.buf = b"" def _read(self): self.buf += os.read(self._fd, self._CHUNK_SIZE) # shortcut struct_size = Inotify.event_prefix.size while len(self.buf) >= struct_size: wd, flags, cookie, length = Inotify.unpack_prefix( self.buf[:struct_size] ) struct_end = struct_size + length name = Inotify.unpack_name(self.buf[struct_size:struct_end]) self.buf = self.buf[struct_end:] if wd not in self.paths: continue path = self.paths[wd] if flags & Inotify.IGNORED: logger.warning( "Got IGNORED event for %s, cleaning watch", path ) self._cleanup_watch(path) continue if flags & Inotify.Q_OVERFLOW: logger.error("Inotify queue overflow") continue ev = Event(path, flags, cookie, name, wd) self._loop.create_task(self._callback(ev)) def _raise_user_watches(self): current_max_watches = sysctl.read(self._MAX_USER_WATCHES) new_max_watchers = current_max_watches + int( current_max_watches * self._WATCHERS_RAISE_COEFF ) logger.info( "Raising %s to %s", self._MAX_USER_WATCHES, new_max_watchers ) sysctl.write(self._MAX_USER_WATCHES, new_max_watchers) def close(self): """ Close watcher. Close inotify fd, remove reader and reset state :return: """ self._loop.remove_reader(self._fd) try: os.close(self._fd) finally: self._reset_state() self._fd = None def watch(self, path, mask): """ Add file to watch :param path: file or directory to watch :param mask: events mask for this watch """ assert isinstance(path, bytes), "Path must be bytes" logger.info("Watching %r", path) retries = 0 while True: try: wd = Inotify.add_watch(self._fd, path, mask) self.paths[wd] = path self.descriptors[path] = wd break except OSError as e: if ( retries < self._MAX_WATCH_RETRIES and e.errno == errno.ENOSPC ): self._raise_user_watches() retries += 1 logger.warning( "Inotify: not enough watches (%r), retrying...", path ) continue logger.error("Inotify failed while watching %r", path) raise def _cleanup_watch(self, path): descriptor = self.descriptors.pop(path, None) if descriptor is not None: self.paths.pop(descriptor, None) def unwatch(self, path): """ Remove file or directory from watch :param path: file or directory to remove watch from """ if path not in self.descriptors: return logger.info("Stop watching %r", path) try: Inotify.rm_watch(self._fd, self.descriptors[path]) finally: self._cleanup_watch(path) async def get_event(self): """ Get watch event :return: `Event` named tuple """ event = await self._queue.get() logger.debug("Inotify event: %s", event) return event defence360agent/subsys/backup_systems.py0000644000000000000000000002630100000000000015506 0ustar import asyncio import functools import logging from datetime import timezone from typing import Callable, Dict, List, Optional from defence360agent.contracts.config import ( ACRONIS, ANTIVIRUS_MODE, AcronisBackup as AcronisBackupConfig, BackupConfig, BackupRestore, CLOUDLINUX, CLOUDLINUX_ON_PREMISE, CLUSTERLOGICS, CPANEL, Core, DIRECTADMIN, PLESK, R1SOFT, SAMPLE_BACKEND, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.internals.cln import BackupNotFound, RestCLN from defence360agent.subsys.panels.cpanel.panel import cPanel from defence360agent.subsys.panels.directadmin.panel import DirectAdmin from defence360agent.subsys.panels.plesk.panel import Plesk if not ANTIVIRUS_MODE: from restore_infected import backup_backends from restore_infected.backup_backends.acronis import BackupFailed from restore_infected.backup_backends_lib import ( BackendNonApplicableError, BackendNotAuthorizedError, ) logger = logging.getLogger(__name__) def get_backend(name): try: return _get_avalible_backends(include_sample=True)[name]() except (KeyError, BackendNonApplicableError): raise ValueError("Backup system is not available: {}".format(name)) def get_available_backends_names() -> List[str]: names = [] # Don't list the CL Backup as available for selection for name, cls in _get_avalible_backends(include_cl=False).items(): try: cls() except BackendNonApplicableError: pass else: names.append(name) return names def _get_avalible_backends( include_sample=False, include_cl=True, ) -> Dict[str, Callable]: backends = { ACRONIS: Acronis, R1SOFT: R1Soft, # https://cloudlinux.atlassian.net/browse/DEF-8806 # CLUSTERLOGICS: ClusterLogics, } if BackupRestore.CL_BACKUP_ALLOWED and include_cl: backends[CLOUDLINUX] = CloudLinux if BackupRestore.CL_ON_PREMISE_BACKUP_ALLOWED: backends[CLOUDLINUX_ON_PREMISE] = CloudLinuxOnPremise if cPanel.is_installed(): backends[CPANEL] = cPanelBackup elif Plesk.is_installed(): backends[PLESK] = PleskBackup elif DirectAdmin.is_installed(): backends[DIRECTADMIN] = DirectAdminBackup if include_sample: backends[SAMPLE_BACKEND] = Sample return backends def get_current_backend() -> Optional[str]: conf = BackupConfig().config_to_dict().get("BACKUP_SYSTEM", {}) return conf.get("enabled") and conf.get("backup_system") async def get_last_backup_timestamp() -> Optional[int]: backend = get_current_backend() if not backend: return None backend_instance = get_backend(backend) # type: BackupSystem return await backend_instance.get_last_backup_timestamp() def transactional(f): async def wrapper(cls, *args, **kwargs): ok = False try: rv = await f(cls, *args, **kwargs) ok = True finally: cls._update_backups_config(enabled=ok) return rv return wrapper class BackupException(Exception): pass class BackupSystem: def __init__(self, name, log_path=None): self.name = name self.log_path = log_path def _update_backups_config(self, enabled): new_conf = { "BACKUP_SYSTEM": { "enabled": enabled, "backup_system": self.name if enabled else None, } } BackupConfig().dict_to_config(new_conf, overwrite=True, validate=True) async def init(self, *args, **kwargs): self._update_backups_config(enabled=True) async def disable(self, delete_backups=False): self._update_backups_config(enabled=False) async def check(self): return {} async def show(self): return {} async def make_backup(self): pass async def check_state(self) -> bool: conf = BackupConfig().config_to_dict().get("BACKUP_SYSTEM", {}) return conf.get("enabled") and conf.get("backup_system") == self.name async def get_last_backup_timestamp(self) -> Optional[int]: return None class PleskBackup(BackupSystem): def __init__(self): super().__init__(PLESK) class cPanelBackup(BackupSystem): def __init__(self): super().__init__(CPANEL) class DirectAdminBackup(BackupSystem): def __init__(self): super().__init__(DIRECTADMIN) class R1Soft(BackupSystem): def __init__(self): super().__init__(R1SOFT) self.backend = backup_backends.backend("r1soft", async_=True) async def show(self) -> dict: info_data = await self.backend.info() return { k: v for k, v in info_data.items() if k in ("username", "timestamp", "ip") } @transactional async def init(self, ip, username, password, encryption_key, **kwargs): await self.backend.init(ip, username, password, encryption_key) class ClusterLogics(BackupSystem): def __init__(self): super().__init__(CLUSTERLOGICS) self.backend = backup_backends.backend(CLUSTERLOGICS, async_=True) async def show(self) -> dict: info_data = await self.backend.info() return { k: v for k, v in info_data.items() if k in ("username", "url", "apikey") } @transactional async def init(self, **kwargs): # 'force' argument (for arconis only) has default value # also, need to use default value for 'url', # assigned inside backend.init del kwargs["force"] await self.backend.init(**kwargs) class Sample(BackupSystem): def __init__(self): super().__init__(SAMPLE_BACKEND) self.backend = backup_backends.backend(self.name, async_=True) class Acronis(BackupSystem): def __init__(self): super().__init__( ACRONIS, "/var/log/%s/%s" % (Core.PRODUCT, AcronisBackupConfig.LOG_NAME), ) self.backend = backup_backends.backend(self.name, async_=True) async def show(self) -> dict: info_data = await self.backend.info() return { k: v for k, v in info_data.items() if k in ("username", "timestamp") } @transactional async def init(self, username, password, force=False, **kwargs): provision = not await self.backend.is_agent_installed() await self.backend.init( username, password, provision=provision, force=force, tmp_dir=Core.TMPDIR, ) async def _list_backups(self, until=None): return await self.backend.backups(until) async def get_last_backup_timestamp(self) -> Optional[int]: backups = await self._list_backups() if backups: return int( max( backup.created.replace(tzinfo=timezone.utc).timestamp() for backup in backups ) ) return None async def check_state(self) -> bool: """if backup exists, than state OK""" try: return bool(await self._list_backups()) except (asyncio.CancelledError, BackendNotAuthorizedError): raise except Exception: logger.exception("Error during checking state") return False class CloudLinuxBase(Acronis): async def show(self) -> dict: info_data = await self.backend.info() info_data["backup_space_used_bytes"] = info_data.pop("usage") info_data["login_url"] = await self.backend.login_url() return info_data async def make_backup(self): logger.info("Making backup") try: await self.backend.make_initial_backup_strict() except BackupFailed as e: logging.exception("CloudLinux backup failed") raise BackupException( str(e) if len(e.args) and e.args[0] else "BackupFailed" ) async def get_backup_progress(self) -> Optional[int]: return await self.backend.get_backup_progress() async def init(self, username, password, force=False, **kwargs): logger.info("Starting %s init" % self.name) provision = not await self.backend.is_agent_installed() await self.backend.init( username, password, provision=provision, force=force, tmp_dir=Core.TMPDIR, ) class CloudLinux(CloudLinuxBase): PAID, UNPAID = "paid", "unpaid" def __init__(self): super().__init__() self.name = CLOUDLINUX @transactional async def init(self, force=False, **kwargs): credentials = await RestCLN.acronis_credentials( server_id=LicenseCLN.get_server_id() ) await super().init( credentials["login"], credentials["password"], force=force, ) class Decorators: @staticmethod def update_credentials_on_unauthorized_error(f): @functools.wraps(f) async def wrapped(self, *args, **kwargs): try: return await f(self, *args, **kwargs) except BackendNotAuthorizedError: await self.init(force=True) return await f(self, *args, **kwargs) return wrapped @Decorators.update_credentials_on_unauthorized_error async def show(self) -> dict: info_data = await super().show() # FIXME: raise exception when server_id is None response = await RestCLN.acronis_check( server_id=LicenseCLN.get_server_id() ) purchased_backup_gb = response.get("size", 0) resize_url = response.get("url", None) info_data["purchased_backup_gb"] = purchased_backup_gb info_data["resize_url"] = resize_url return info_data @Decorators.update_credentials_on_unauthorized_error async def make_backup(self): await super().make_backup() @Decorators.update_credentials_on_unauthorized_error async def get_backup_progress(self) -> Optional[int]: return await super().get_backup_progress() @Decorators.update_credentials_on_unauthorized_error async def get_last_backup_timestamp(self) -> Optional[int]: return await super().get_last_backup_timestamp() @Decorators.update_credentials_on_unauthorized_error async def check_state(self) -> bool: return await super().check_state() async def check(self) -> dict: try: content = await RestCLN.acronis_check( server_id=LicenseCLN.get_server_id() ) except BackupNotFound as e: return {"status": self.UNPAID, "url": e.add_used_space()} return {"status": self.PAID, "size": content.get("size")} async def disable(self, delete_backups=False): await super().disable() if delete_backups: await RestCLN.acronis_remove(server_id=LicenseCLN.get_server_id()) class CloudLinuxOnPremise(CloudLinuxBase): def __init__(self): super().__init__() self.name = CLOUDLINUX_ON_PREMISE @transactional async def init(self, *args, **kwargs): await super().init(*args, **kwargs) defence360agent/subsys/clcagefs.py0000644000000000000000000001744700000000000014234 0ustar # -*- coding: utf-8 -*- # Copyright © Cloud Linux GmbH & Cloud Linux Software, Inc # 2010-2018 All Rights Reserved # # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENSE.TXT # import os import re import subprocess CAGEFS_MP_FILENAME = "/etc/cagefs/cagefs.mp" CAGEFSCTL_TOOL = "/usr/sbin/cagefsctl" class CagefsMpConflict(Exception): def __init__(self, new_item, existing_item): self._msg = ( "Conflict in adding '%s' to %s because of pre-existing " "alternative specification: '%s'" % (new_item, CAGEFS_MP_FILENAME, existing_item) ) def __str__(self): return self._msg class CagefsMpItem: PREFIX_LIST = b"@!%" _PREFIX_MOUNT_RW = b"" _PREFIX_MOUNT_RO = b"!" def __init__(self, arg): """Constructor :param arg: Is either path to add to cagefs.mp or a raw line is read from cagefs.mp :param prefix: The same as adding prefix '!' to arg before passing it to ctor""" if arg[:1] == b"#": # is a comment? then init as dummy self._path_spec = None elif arg.strip() == b"": # init as dummy for empty lines self._path_spec = None else: self._path_spec = arg def mode(self, mode): """Specify mode as in fluent constructor""" if self.prefix() == b"@" and mode is not None: self._path_spec = b"%s,%03o" % (self._path_spec, mode) return self def __str__(self): return os.fsdecode(self._path_spec) @staticmethod def _add_slash(path): if path == b"": return b"/" if path[-1] != b"/"[0]: return path + b"/" return path def pre_exist_in(self, another): adopted = CagefsMpItem._adopt(another) # overkill: just to keep strictly to comparing NULL objects principle if self.is_dummy() or adopted.is_dummy(): return False this_path = CagefsMpItem._add_slash(self.path()) test_preexist_in_path = CagefsMpItem._add_slash(adopted.path()) return this_path.startswith(test_preexist_in_path) def is_compatible_by_prefix_with(self, existing): adopted = CagefsMpItem._adopt(existing) # overkill: just to keep strictly to comparing NULL objects principle if self.is_dummy() or adopted.is_dummy(): return False if self.prefix() == adopted.prefix(): return True prefix_compatibility_map = { CagefsMpItem._PREFIX_MOUNT_RW: [CagefsMpItem._PREFIX_MOUNT_RO] } null_options = [] return self.prefix() in prefix_compatibility_map.get( adopted.prefix(), null_options ) def is_dummy(self): return self._path_spec is None @staticmethod def _adopt(x): if isinstance(x, CagefsMpItem): return x else: return CagefsMpItem(x) @staticmethod def _cut_off_mode(path_spec): """Cut off mode from path spec like @/var/run/screen,777 Only one comma per path spec is allowed ;-)""" return path_spec.split(b",")[0] @staticmethod def _cut_off_prefix(path_spec): return path_spec.lstrip(CagefsMpItem.PREFIX_LIST) def path(self): return CagefsMpItem._cut_off_prefix( CagefsMpItem._cut_off_mode(self._path_spec) ) def prefix(self): if self._path_spec != self.path(): return self._path_spec[0:1] else: return b"" def spec(self): return self._path_spec def is_cagefs_present(): return os.path.exists(CAGEFSCTL_TOOL) def _mk_mount_dir_setup_perm(path, mode=0o755, owner_id=None, group_id=None): # -1 means 'unchanged' if group_id is None: group_id = -1 if owner_id is None: owner_id = -1 if not os.path.isdir(path): os.mkdir(path) if mode is not None: os.chmod(path, mode) os.chown(path, owner_id, group_id) def setup_mount_dir_cagefs( path, added_by, mode=0o755, owner_id=None, group_id=None, prefix=b"", remount_cagefs=True, ): """ Add mount point to /etc/cagefs/cagefs.mp :param path: Directory path to be added in cagefs.mp and mounted from within setup_mount_dir_cagefs(). If this directory does not exist, then it is created. :param added_by: package or component, mount dir relates to, or whatever will stay in cagefs.mp with "# added by..." comment :param mode: If is not None: Regardless of whether directory exists or not prior this call, it's permissions will be set to mode. :param owner_id: Regardless of whether directory exists or not prior this call, it's owner id will be set to. If None, the owner won't be changed. :param group_id: Regardless of whether directory exists or not prior this call, it's group id will be set to. If None, the group won't be changed. :param prefix: Mount point prefix. Default is mount as RW. Pass '!' to add read-only mount point. Refer CageFS section at http://docs.cloudlinux.com/ for more options. :param remount_cagefs: If True, cagefs skeleton will be automatically remounted to apply changes. :returns: None Propagates native EnvironmentError if no CageFS installed or something else goes wrong. Raises CagefsMpConflict if path is already specified in cagefs.mp, but in a way which is opposite to mount_as_readonly param. """ _mk_mount_dir_setup_perm(path, mode, owner_id, group_id) # Create cagefs.mp if absent. It will be merged when cagefsctl --init. if not os.path.exists(CAGEFS_MP_FILENAME): subprocess.call([CAGEFSCTL_TOOL, "--create-mp"]) subprocess.call([CAGEFSCTL_TOOL, "--check-mp"]) # ^^ # Hereafter we will not care if there was # 'no newline at the end of file' cagefs_mp = open(CAGEFS_MP_FILENAME, "rb+") try: new_item = CagefsMpItem(prefix + path).mode(mode) trim_nl_iter = (file_line.rstrip() for file_line in cagefs_mp) pre_exist_option = [ x for x in trim_nl_iter if new_item.pre_exist_in(x) ] if not pre_exist_option: cagefs_mp.seek(0, 2) # 2: seek to the end of file # no newline is allowed added_by = added_by.replace("\n", " ") cagefs_mp.write( b"# next line is added by " + added_by.encode("utf-8") + b"\n" ) cagefs_mp.write(new_item.spec() + b"\n") cagefs_mp.close() if remount_cagefs: subprocess.call([CAGEFSCTL_TOOL, "--remount-all"]) elif not new_item.is_compatible_by_prefix_with(pre_exist_option[-1]): raise CagefsMpConflict(new_item, pre_exist_option[-1]) finally: cagefs_mp.close() def _get_cagefs_mp_lines(): with open(CAGEFS_MP_FILENAME, "rb") as f: return f.readlines() def _write_cagefs_mp_lines(lines): with open(CAGEFS_MP_FILENAME, "wb") as f: return f.writelines(lines) def remove_mount_dir_cagefs(path, remount_cagefs=True): """ Remove mount points matching given path from cagefs.mp file :param str path: Path that should be removed from file. :param bool remount_cagefs: Remount cagefs skeleton or not :return: Nothing """ lines = _get_cagefs_mp_lines() r = re.compile( rb"^[%s]?%s(,\d+)?$" % (CagefsMpItem.PREFIX_LIST, re.escape(path)) ) lines_with_excluded_path = (line for line in lines if not r.match(line)) _write_cagefs_mp_lines(lines_with_excluded_path) if remount_cagefs: subprocess.call([CAGEFSCTL_TOOL, "--remount-all"]) defence360agent/subsys/features/0000755000000000000000000000000000000000000013714 5ustar defence360agent/subsys/features/__init__.py0000644000000000000000000000000000000000000016013 0ustar defence360agent/subsys/features/__pycache__/0000755000000000000000000000000000000000000016124 5ustar defence360agent/subsys/features/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000031200000000000023320 0ustar r_jdS)Nr]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/__init__.pyrsrdefence360agent/subsys/features/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031200000000000022361 0ustar r_jdS)Nr]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/__init__.pyrsrdefence360agent/subsys/features/__pycache__/abstract_feature.cpython-311.opt-1.pyc0000644000000000000000000002421500000000000025107 0ustar r_jddlZddlZddlZddlmZmZddlmZddlZej e Z GddZ dZ GddeZGd d eZGd d e ZdS)N)ABCMetaabstractmethod)isclosec*eZdZdZdZdZdZdZdZdZ dS) FeatureStatuserror installed installingremoving not_installedmanaged_by_lveznot-supported-by-cl-soloN) __name__ __module__ __qualname__ERROR INSTALLED INSTALLINGREMOVING NOT_INSTALLEDMANAGED_BY_LVENOT_SUPPORTED_BY_CL_SOLOe/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/abstract_feature.pyrr s4 EIJH#M%N9rrcfd}|S)z If Easy Apache 4 not installed, then raising an error :raises FeatureError :param func: install or remove func :return func: cKtjdstd|i|d{VS)Nz/etc/cpanel/ea4/is_ea4z3Hardened PHP is compatible only with Easy Apache 4!)ospathisfile FeatureError)argskwargsfuncs rwrapperzea4_only..wrappers\w~~677 E T4*6*********rrr#r$s` rea4_onlyr&s#+++++ NrceZdZdZdS)r z*Feature operation can't be performed errorNrrr__doc__rrrr r (s44Drr ceZdZdZdS) FeatureNoticez+Feature operation can't be performed noticeNr(rrrr+r+.s55Drr+c,eZdZdZdZdZgZddZdZe dZ e dZ e dZ edZe dZd efd Zed efd Zed efd Zed efd ZedZedZdZdS)AbstractFeatureNcX|js Jd|js Jd||_dS)Nzvariable isn't set!)INSTALL_LOG_FILE_MASKREMOVE_LOG_FILE_MASK_sink)selfsinks r__init__zAbstractFeature.__init__:s=)@@+@@@)(??*???( rcHK|d{V|_|SN)check_installed is_installedr2s rinitzAbstractFeature.init@s1"&"6"6"8"8888888 rc6||jSr6) _get_live_logr/r9s rinstallation_live_logz%AbstractFeature.installation_live_logDs!!$"<===rc6||jSr6)r<r0r9s rremoval_live_logz AbstractFeature.removal_live_logHs!!$";<<%>"]N3s88,,88::MM-00!                  V%9:   55 s5CBB7* C7B;;C>B;?CC$#C$c*tj|S)zo :param str log_mask: regexp of log file path :return: list of files found by log_mask )glob)log_masks r_ls_logszAbstractFeature._ls_logsZs y"""rcntt|j||dS)a Returns path of log file, which used by some process. If log file used by process, assuming that installation/removal is in the progress :param str file_mask: regexp of log file path :return: str path of log, used by some process N)nextfilterrTrX)rO file_masks rr<zAbstractFeature._get_live_logbs-F3. Y0G0GHH$OOOrreturnc^K|jrdS|jrdS|d{VS)NFT)r=r?_check_installed_implr9s rr7zAbstractFeature.check_installednsJ  % 5   4//111111111rc KdS)NFrr9s rr_z%AbstractFeature._check_installed_implus urc"Kt)z :return str: path to log file with installation process :raise FeatureError: when feature is already installed, concurrent operation is in progress, feature is not applicable for given setup, etc. NotImplementedErrorr9s rinstallzAbstractFeature.installys"###rc"Ktr6rbr9s rremovezAbstractFeature.removes!###rcfd}|S)a Checks before operation if similar or mutually exclusive operation is in the progress. Checks if there are condition why operation can't be performed. :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted cK|jrtd|jr'td|j|jp|d{VS)Nz$Wait until uninstalling is finished!z{} is already installed)r?r r8r+formatNAMEr=r2r#s rr$z>AbstractFeature.raise_if_shouldnt_install_now..wrappers{$ "#IJJJ" #-44TY??-AttDzz1A1A1A1A1A1A Arrr%s` rraise_if_shouldnt_install_nowz-AbstractFeature.raise_if_shouldnt_install_nows( B B B B Brcfd}|S)z :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted cK|jrtd|js'td|j|jp|d{VS)Nz$Wait until installation is finished!z+Can't delete {}, because it's not installed)r=r r8r+rirjr?rks rr$z=AbstractFeature.raise_if_shouldnt_remove_now..wrappers) "#IJJJ& #AHH  (<$$t**,<,<,<,<,<,< >X>==X=  [ ##\# P P[ P2t2222T^$s$$$^$$c$$$^$\0\,     rr-) metaclass)rVloggingrabcrrmathrrF getLoggerrloggerrr& Exceptionr r+r-rrrrs4  ''''''''  8 $ $::::::::$     9        L   U U U U U U U U U U U rdefence360agent/subsys/features/__pycache__/abstract_feature.cpython-311.pyc0000644000000000000000000002421500000000000024150 0ustar r_jddlZddlZddlZddlmZmZddlmZddlZej e Z GddZ dZ GddeZGd d eZGd d e ZdS)N)ABCMetaabstractmethod)isclosec*eZdZdZdZdZdZdZdZdZ dS) FeatureStatuserror installed installingremoving not_installedmanaged_by_lveznot-supported-by-cl-soloN) __name__ __module__ __qualname__ERROR INSTALLED INSTALLINGREMOVING NOT_INSTALLEDMANAGED_BY_LVENOT_SUPPORTED_BY_CL_SOLOe/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/features/abstract_feature.pyrr s4 EIJH#M%N9rrcfd}|S)z If Easy Apache 4 not installed, then raising an error :raises FeatureError :param func: install or remove func :return func: cKtjdstd|i|d{VS)Nz/etc/cpanel/ea4/is_ea4z3Hardened PHP is compatible only with Easy Apache 4!)ospathisfile FeatureError)argskwargsfuncs rwrapperzea4_only..wrappers\w~~677 E T4*6*********rrr#r$s` rea4_onlyr&s#+++++ NrceZdZdZdS)r z*Feature operation can't be performed errorNrrr__doc__rrrr r (s44Drr ceZdZdZdS) FeatureNoticez+Feature operation can't be performed noticeNr(rrrr+r+.s55Drr+c,eZdZdZdZdZgZddZdZe dZ e dZ e dZ edZe dZd efd Zed efd Zed efd Zed efd ZedZedZdZdS)AbstractFeatureNcX|js Jd|js Jd||_dS)Nzvariable isn't set!)INSTALL_LOG_FILE_MASKREMOVE_LOG_FILE_MASK_sink)selfsinks r__init__zAbstractFeature.__init__:s=)@@+@@@)(??*???( rcHK|d{V|_|SN)check_installed is_installedr2s rinitzAbstractFeature.init@s1"&"6"6"8"8888888 rc6||jSr6) _get_live_logr/r9s rinstallation_live_logz%AbstractFeature.installation_live_logDs!!$"<===rc6||jSr6)r<r0r9s rremoval_live_logz AbstractFeature.removal_live_logHs!!$";<<%>"]N3s88,,88::MM-00!                  V%9:   55 s5CBB7* C7B;;C>B;?CC$#C$c*tj|S)zo :param str log_mask: regexp of log file path :return: list of files found by log_mask )glob)log_masks r_ls_logszAbstractFeature._ls_logsZs y"""rcntt|j||dS)a Returns path of log file, which used by some process. If log file used by process, assuming that installation/removal is in the progress :param str file_mask: regexp of log file path :return: str path of log, used by some process N)nextfilterrTrX)rO file_masks rr<zAbstractFeature._get_live_logbs-F3. Y0G0GHH$OOOrreturnc^K|jrdS|jrdS|d{VS)NFT)r=r?_check_installed_implr9s rr7zAbstractFeature.check_installednsJ  % 5   4//111111111rc KdS)NFrr9s rr_z%AbstractFeature._check_installed_implus urc"Kt)z :return str: path to log file with installation process :raise FeatureError: when feature is already installed, concurrent operation is in progress, feature is not applicable for given setup, etc. NotImplementedErrorr9s rinstallzAbstractFeature.installys"###rc"Ktr6rbr9s rremovezAbstractFeature.removes!###rcfd}|S)a Checks before operation if similar or mutually exclusive operation is in the progress. Checks if there are condition why operation can't be performed. :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted cK|jrtd|jr'td|j|jp|d{VS)Nz$Wait until uninstalling is finished!z{} is already installed)r?r r8r+formatNAMEr=r2r#s rr$z>AbstractFeature.raise_if_shouldnt_install_now..wrappers{$ "#IJJJ" #-44TY??-AttDzz1A1A1A1A1A1A Arrr%s` rraise_if_shouldnt_install_nowz-AbstractFeature.raise_if_shouldnt_install_nows( B B B B Brcfd}|S)z :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted cK|jrtd|js'td|j|jp|d{VS)Nz$Wait until installation is finished!z+Can't delete {}, because it's not installed)r=r r8r+rirjr?rks rr$z=AbstractFeature.raise_if_shouldnt_remove_now..wrappers) "#IJJJ& #AHH  (<$$t**,<,<,<,<,<,< >X>==X=  [ ##\# P P[ P2t2222T^$s$$$^$$c$$$^$\0\,     rr-) metaclass)rVloggingrabcrrmathrrF getLoggerrloggerrr& Exceptionr r+r-rrrrs4  ''''''''  8 $ $::::::::$     9        L   U U U U U U U U U U U rdefence360agent/subsys/features/__pycache__/kernel_care.cpython-311.opt-1.pyc0000644000000000000000000001445700000000000024052 0ustar r_jddlZddlZddlZddlmZddlmZmZddlm Z m Z m Z ddl m Z ddlmZejeZGdde ZdS) N)Core) OsReleaseInforun_cmd_and_log_in_own_cgroup)AbstractFeature FeatureError FeatureStatus)utils) exceptionsceZdZdZdZdejzZdZdZ dezZ dezZ dezZ dZ d Ze e egZd d d d dZdefdZfdZejdZejdZdZdZdZxZS) KernelCarez4/var/imunify360/plesk-previous-kernelcare-stats.jsonzrgs 111111 "!!!!!000000  8 $ $d/d/d/d/d/d/d/d/d/d/rdefence360agent/subsys/features/__pycache__/kernel_care.cpython-311.pyc0000644000000000000000000001445700000000000023113 0ustar r_jddlZddlZddlZddlmZddlmZmZddlm Z m Z m Z ddl m Z ddlmZejeZGdde ZdS) N)Core) OsReleaseInforun_cmd_and_log_in_own_cgroup)AbstractFeature FeatureError FeatureStatus)utils) exceptionsceZdZdZdZdejzZdZdZ dezZ dezZ dezZ dZ d Ze e egZd d d d dZdefdZfdZejdZejdZdZdZdZxZS) KernelCarez4/var/imunify360/plesk-previous-kernelcare-stats.jsonzrgs 111111 "!!!!!000000  8 $ $d/d/d/d/d/d/d/d/d/d/rdefence360agent/subsys/features/abstract_feature.py0000644000000000000000000001375200000000000017614 0ustar import glob import logging import os from abc import ABCMeta, abstractmethod from math import isclose import psutil logger = logging.getLogger(__name__) class FeatureStatus: ERROR = "error" INSTALLED = "installed" INSTALLING = "installing" REMOVING = "removing" NOT_INSTALLED = "not_installed" MANAGED_BY_LVE = "managed_by_lve" NOT_SUPPORTED_BY_CL_SOLO = "not-supported-by-cl-solo" def ea4_only(func): """ If Easy Apache 4 not installed, then raising an error :raises FeatureError :param func: install or remove func :return func: """ async def wrapper(*args, **kwargs): if not os.path.isfile("/etc/cpanel/ea4/is_ea4"): raise FeatureError( "Hardened PHP is compatible only with Easy Apache 4!" ) return await func(*args, **kwargs) return wrapper class FeatureError(Exception): """Feature operation can't be performed error""" pass class FeatureNotice(FeatureError): """Feature operation can't be performed notice""" pass class AbstractFeature(metaclass=ABCMeta): NAME = "AbstractFeature" INSTALL_LOG_FILE_MASK = None # type: str REMOVE_LOG_FILE_MASK = None # type: str _CMD_LIST = [] # type: List[str] def __init__(self, sink=None): assert self.INSTALL_LOG_FILE_MASK, "variable isn't set!" assert self.REMOVE_LOG_FILE_MASK, "variable isn't set!" self._sink = sink async def init(self): self.is_installed = await self.check_installed() return self @property def installation_live_log(self): return self._get_live_log(self.INSTALL_LOG_FILE_MASK) @property def removal_live_log(self): return self._get_live_log(self.REMOVE_LOG_FILE_MASK) @classmethod def _log_still_used(cls, log_file): """Checks if any processes are using log file.""" try: with open(log_file + ".pid") as pf: pid, creation_time = pf.read().strip().split() return isclose( psutil.Process(int(pid)).create_time(), float.fromhex(creation_time), rel_tol=1e-12, ) except (OSError, ValueError, psutil.NoSuchProcess): return False @staticmethod def _ls_logs(log_mask): """ :param str log_mask: regexp of log file path :return: list of files found by log_mask """ return glob.glob(log_mask) @classmethod def _get_live_log(cls, file_mask): """ Returns path of log file, which used by some process. If log file used by process, assuming that installation/removal is in the progress :param str file_mask: regexp of log file path :return: str path of log, used by some process """ return next(filter(cls._log_still_used, cls._ls_logs(file_mask)), None) async def check_installed(self) -> bool: if self.installation_live_log: return False if self.removal_live_log: return True return await self._check_installed_impl() @abstractmethod async def _check_installed_impl(self) -> bool: return False @abstractmethod async def install(self) -> str: """ :return str: path to log file with installation process :raise FeatureError: when feature is already installed, concurrent operation is in progress, feature is not applicable for given setup, etc. """ raise NotImplementedError() @abstractmethod async def remove(self) -> str: raise NotImplementedError() @staticmethod def raise_if_shouldnt_install_now(func): """ Checks before operation if similar or mutually exclusive operation is in the progress. Checks if there are condition why operation can't be performed. :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted """ async def wrapper(self): # check if the operation is in progress if self.removal_live_log: raise FeatureError("Wait until uninstalling is finished!") elif self.is_installed: raise FeatureNotice( "{} is already installed".format(self.NAME) ) return self.installation_live_log or await func(self) return wrapper @staticmethod def raise_if_shouldnt_remove_now(func): """ :raises FeatureError: if operation couldn't be performed :returns str msg: log path if already ongoing operation :returns continue function isntall/remove: if operation is permitted """ async def wrapper(self): # check if the operation is in progress if self.installation_live_log: raise FeatureError("Wait until installation is finished!") elif not self.is_installed: raise FeatureNotice( "Can't delete {}, because it's not installed".format( self.NAME ) ) return self.removal_live_log or await func(self) return wrapper async def status(self): if self.installation_live_log: msg = "{} is installing".format(self.NAME) status = FeatureStatus.INSTALLING elif self.removal_live_log: msg = "{} is removing".format(self.NAME) status = FeatureStatus.REMOVING elif await self.check_installed(): msg = "{} is installed".format(self.NAME) status = FeatureStatus.INSTALLED else: msg = "{} is not installed".format(self.NAME) status = FeatureStatus.NOT_INSTALLED return { "items": { "message": msg, "status": status, } } defence360agent/subsys/features/kernel_care.py0000644000000000000000000001043500000000000016543 0ustar import logging import os import json from defence360agent.contracts.config import Core from defence360agent.utils import ( OsReleaseInfo, run_cmd_and_log_in_own_cgroup, ) from defence360agent.subsys.features.abstract_feature import ( AbstractFeature, FeatureError, FeatureStatus, ) from defence360agent import utils from defence360agent.rpc_tools import exceptions logger = logging.getLogger(__name__) class KernelCare(AbstractFeature): KC_PROPERTIES = "/var/imunify360/plesk-previous-kernelcare-stats.json" KC_SCRIPT_URL = ( "https://repo.cloudlinux.com/kernelcare/kernelcare_install.sh" ) LOG_DIR = "/var/log/%s" % Core.PRODUCT NAME = "KernelCare" BIN_PATH = "/usr/bin/kcarectl" INSTALL_LOG_FILE_MASK = "%s/install-kernelcare.log.*" % LOG_DIR REMOVE_LOG_FILE_MASK = "%s/remove-kernelcare.log.*" % LOG_DIR INSTALL_CMD = "curl -s %s | bash" % KC_SCRIPT_URL REMOVE_CMD_REDHAT = "yum remove -y kernelcare" REMOVE_CMD_DEBIAN = "apt-get -y remove kernelcare" _CMD_LIST = [INSTALL_CMD, REMOVE_CMD_REDHAT, REMOVE_CMD_DEBIAN] STATUS_MESSAGE = { 0: "Host is updated to the latest patch level", 1: "There are no applied patches", 2: "There are new not applied patches", 3: "Kernel is unsupported", } async def _check_installed_impl(self) -> bool: return os.path.exists(self.BIN_PATH) async def status(self): """ :raises FeatureError: if kernelcare returns unexpected error :return: str: feature's current status """ status = await super().status() is_feature_installed = ( status["items"]["status"] == FeatureStatus.INSTALLED ) if not is_feature_installed: return status ret, out, err = await self.get_output_kcarectl("--status") try: # EDF is obsolete since 6.1 status["items"]["edf_supported"] = False status["items"]["message"] = self.STATUS_MESSAGE[ret] except KeyError: raise FeatureError( "Unknown error occured while getting status from kcarectl. " f"stdout: [{out}], stderr: [{err}], return code: [{ret}]" ) return status @AbstractFeature.raise_if_shouldnt_install_now async def install(self): # Runs as a transient unit: the KernelCare RPM's %prein scriptlet # needs an LSM domain transition on exec, and its dnf solve plus the # SELinux policy rebuild must not be charged to the agent's cgroup. return await run_cmd_and_log_in_own_cgroup( self.INSTALL_CMD, self.INSTALL_LOG_FILE_MASK, env={"DEBIAN_FRONTEND": "noninteractive"}, ) @AbstractFeature.raise_if_shouldnt_remove_now async def remove(self): if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: command = self.REMOVE_CMD_DEBIAN else: command = self.REMOVE_CMD_REDHAT return await run_cmd_and_log_in_own_cgroup( command, self.REMOVE_LOG_FILE_MASK ) async def get_plugin_info(self): try: output = await self.run_kcarectl("--plugin-info", "--json") except FileNotFoundError: raise exceptions.RpcError("kcarectl not found") except utils.CheckRunError as e: if not (e.returncode == 2 and b"--json" in e.stderr): raise # reraise as is else: # unrecognized arguments: --json # use RpcError, to get an error raise exceptions.RpcError( "Your kcarectl version doesn't support --json option." " Please, update to kernelcare-2.15-2 or newer." ) try: results = json.loads(output.decode().partition("--START--")[-1]) except ValueError: raise exceptions.RpcError( "Can't decode kcarectl output as json." " Try updating to the latest kernelcare version." ) return results async def run_kcarectl(self, *options): return await utils.check_run((self.BIN_PATH,) + options) async def get_output_kcarectl(self, *options): ret, out, err = await utils.run([self.BIN_PATH, *options]) return ret, out.decode(), err.decode() defence360agent/subsys/notifier.py0000644000000000000000000000351100000000000014267 0ustar """Send events via Notification service""" import asyncio import base64 import json SOCKET_PATH = "/opt/imunify360/lib/event.sock" SOCKET_TIMEOUT = 10.0 # seconds _LEN_BYTES = 4 _MAX_SIZE = 1024 * 1024 CONFIG_UPDATED_EVENT_ID = "CONFIG_UPDATED" USER_SCAN_STARTED_EVENT_ID = "USER_SCAN_STARTED" USER_SCAN_FINISHED_EVENT_ID = "USER_SCAN_FINISHED" USER_SCAN_MALWARE_FOUND_EVENT_ID = "USER_SCAN_MALWARE_FOUND" CUSTOM_SCAN_STARTED_EVENT_ID = "CUSTOM_SCAN_STARTED" CUSTOM_SCAN_FINISHED_EVENT_ID = "CUSTOM_SCAN_FINISHED" CUSTOM_SCAN_MALWARE_FOUND_EVENT_ID = "CUSTOM_SCAN_MALWARE_FOUND" SCRIPT_BLOCKED_EVENT_ID = "SCRIPT_BLOCKED" def _prepare_event(event_id: str, user: str, body: dict) -> bytes: event = json.dumps( { "event_id": event_id, "user": user, "body": base64.b64encode(json.dumps(body).encode("utf-8")).decode( "utf-8" ), } ) binary = event.encode("utf-8") if len(binary) > _MAX_SIZE: raise Exception( "message size {} exceeds limit of {}".format( len(binary), _MAX_SIZE ) ) return len(binary).to_bytes(_LEN_BYTES, byteorder="big") + binary async def _send_event(event: bytes) -> None: _, writer = await asyncio.open_unix_connection(SOCKET_PATH) try: writer.write(event) await writer.drain() finally: writer.close() async def trigger_event(event_id: str, user: str, body: dict) -> None: """Send an event with given event_id and user, having given body.""" event = _prepare_event(event_id, user, body) await asyncio.wait_for(_send_event(event), SOCKET_TIMEOUT) async def config_updated() -> None: """Send CONFIG_UPDATED event. This forces imunify-notifier to reread its config.""" await trigger_event(CONFIG_UPDATED_EVENT_ID, "", {}) defence360agent/subsys/panels/0000755000000000000000000000000000000000000013360 5ustar defence360agent/subsys/panels/__init__.py0000644000000000000000000000000000000000000015457 0ustar defence360agent/subsys/panels/__pycache__/0000755000000000000000000000000000000000000015570 5ustar defence360agent/subsys/panels/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000031000000000000022762 0ustar r_jdS)Nr[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/__init__.pyrsrdefence360agent/subsys/panels/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000031000000000000022023 0ustar r_jdS)Nr[/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/__init__.pyrsrdefence360agent/subsys/panels/__pycache__/base.cpython-311.opt-1.pyc0000644000000000000000000003607200000000000022153 0ustar r_jS$DddlZddlmZmZddlmZddlmZddlm Z ddl m Z ddl m Z mZmZmZddlmZmZgd Zd ZGd d e ZGd deZGddeZeGddZGddeZdZGddeZdZdS)N)ABCabstractmethod) defaultdict) dataclass)IntEnum)Path)DictListOptionalSet)APIError IPEchoAPI) 202122255380110443587993995z generic panelceZdZdZdZdZdS) UserLevelN)__name__ __module__ __qualname__ADMINRESSELER REGULAR_USERW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/base.pyrrs EHLLLr&rceZdZdS)PanelExceptionNrr r!r%r&r'r)r)"Dr&r)ceZdZdS)InvalidTokenExceptionNr*r%r&r'r-r-&r+r&r-c8eZdZUeed<eed<eed<eed<dS) DomainDatadocrootdomaintypeusernameN)rr r!str__annotations__r%r&r'r/r/*s4 LLL KKK IIIMMMMMr&r/cfeZdZdZdZdgezdgezdgdgdddZeZgZ e e d Z e d Z e d Ze d Ze d*dZe d*dZe dZe deefdZe deeeeffdZe deeeeffdZe defdZdeeeeefffdZdefdZdefdZe dZde fdZ!e de"efdZ#e dede$fdZ%e dede&efdZ'e defd Z(e d d!d"Z)e deeeffd#Z*e d$e dd fd%Z+deeeeffd&Z,d'Z-e d(ede.e/fd)Z0d S)+ AbstractPanelzTAbstract class that provides only basic hosting panel integration functionality.MINIMAL465113)inout)rrrr)rrrr:123)tcpudpcdS)z\ Checks if hosting panel installed on the known path :return: bool: Nr%clss r' is_installedzAbstractPanel.is_installedDs  r&cL tjS#t$rYdSwxYw)zb Stub with external IP as currently only implementation for cPanel needed )r server_ipr rAs r' get_server_ipzAbstractPanel.get_server_ipMs8  &(( (   22 s  ##c KdSNr%rAs r'versionzAbstractPanel.versionXs tr&cK|jSrI)NAMErAs r'namezAbstractPanel.name\s xr&Nc KdS)zM Registers and enables Imunify360 UI plugin in hosting panel Nr%selfrMs r'enable_imunify_pluginz#AbstractPanel.enable_imunify_plugin` r&c KdS)zC UnRegisters Imunify360 UI plugin in hosting panel Nr%rOs r'disable_imunify_pluginz$AbstractPanel.disable_imunify_plugingrRr&c KdS)zP Returns domains hosted via control panel :return: list Nr%rPs r'get_user_domainszAbstractPanel.get_user_domainsn r&returnc KdS)zO Returns system users from hosting panel :return: list Nr%rVs r' get_userszAbstractPanel.get_usersvrXr&c KdS)zA Returns dict with domain to list of users pairs Nr%rVs r'get_domain_to_ownerz!AbstractPanel.get_domain_to_owner~rRr&c KdS)zA Returns dict with user to list of domains pairs Nr%rVs r'get_domains_per_userz"AbstractPanel.get_domains_per_userrRr&c KdS)z# Returns panel url Nr%)rPr3s r'panel_user_linkzAbstractPanel.panel_user_linkrRr&cNKd|d{VDS)z7 Returns dict with user to email pairs ci|]}|ddd S)rE)emaillocaler%).0users r' z2AbstractPanel.get_user_details..s2    B"--   r&N)r[rVs r'get_user_detailszAbstractPanel.get_user_detailssC   "nn........    r&cnKtt|d{VSrI)lenlistr[rVs r' users_countzAbstractPanel.users_counts84dnn........//000r&datacnd}|jdkr tj|j}|j}|j|fS)z Performs actions to distinguish endusers from admins :param protocol: _RpcServerProtocol :param data: parsed params :returns (user_type, user_name) Nr)_uidpwdgetpwuidpw_namerg)rPprotocolrnrMpws r' authenticatezAbstractPanel.authenticates= =A  hm,,B:D}d""r&ctrINotImplementedErrorrAs r'get_modsec_config_pathz$AbstractPanel.get_modsec_config_paths!!r&cdS)z( Return Conflict status Fr%rVs r'get_SMTP_conflict_statusz&AbstractPanel.get_SMTP_conflict_statuss ur&cdSrIr%rVs r'basedirszAbstractPanel.basedirss r&home_dircRt|j}|SrI)rresolveparent)rBrbase_dirs r' base_home_dirzAbstractPanel.base_home_dirs!>>))++2r&c, ||}t|}||}n#tt f$rYdSwxYwddlm}t|ddx}r|}t|dz |z S)Nr) MalwareTuneRAPID_SCAN_BASEDIR_OVERRIDEz.rapid-scan-db) rrr relative_to ValueError RuntimeError defence360agent.contracts.configrgetattrr4)rBrr resolved_hometailrrapid_scan_basedir_overrides r'get_rapid_scan_db_dirz#AbstractPanel.get_rapid_scan_db_dirs ((22H NN2244M ,,X66DDL)   44  A@@@@@*1 6+ +  & 33H8..5666sA AA#"A#cKt)z Returns registration key from panel, if possible, raise PanelException if not successful (or wrong panel key provided), or NoImplemented if method not supported by the panel. rxrAs r' retrieve_keyzAbstractPanel.retrieve_keys"!r&)rgc KdS)zD Notify a customer using the panel internal tooling Nr%)rB message_typeparamsrgs r'notifyzAbstractPanel.notify tr&c KdS)z5 :return dict with docroot to domain Nr%rVs r' list_docrootszAbstractPanel.list_docrootsrRr&myimunify_enabledc KdS)zK Switch UI panel configuration between Im360 and MyImunify Nr%)rBrs r'switch_ui_configzAbstractPanel.switch_ui_configrr&cK|d{V}tt}|D] \}}|||!|S)z Domain to docroot list mapping Patchman expects a subdomain to be listed separately from main domain :return: dict with domain to list of docroots N)rrrlitemsappend)rP doc_roots domain_pathsdoc_root domain_names r'get_domain_pathszAbstractPanel.get_domain_pathssx,,........ "4(( %.__%6%6 7 7 !Hk  % , ,X 6 6 6 6r&c K|d{V}|d{V}|d{V}|d{V}g}|D]}||}|d}|d} |dd} |dt t j} |dd} ||g} g}| D]0}||g}|||d1|||| | | | |d |S) NrdrerrElevel suspendedF)r1paths)r3rdlanguagerrrdomains) r[rr_rigetintrr$r)rP panel_usersr user_domains user_detailsusers user_namedetailsrdrerrrruser_domain_pathsrrs r'patchman_userszAbstractPanel.patchman_userss NN,,,,,,,, !2244444444 !6688888888 !2244444444 $  I"9-GG$EX&F[[2..FKKY-C)D)DEEE K77I"&&y"55G " &   $((b99!(("-!& LL )" &$"!*0     r&r3c"KtrIrx)rBr3s r'get_user_domains_detailsz&AbstractPanel.get_user_domains_details/s!###r&rI)1rr r!__doc__rLTCP_PORTS_COMMON OPEN_PORTSr) exceptionsmtp_allow_users classmethodrrCrGrJrMrQrTrWr r4r[r r]r_rarirrmdictrvrzboolr|r r~rrr rrrrrrrrlr/rr%r&r'r7r72sB D',,7--  ,++333   JI  ^[ [[[   ^    ^   ^  c   ^  4T#Y+?   ^  Dd3i,@   ^     ^  S$sCx.-@(A    131111#4####(""["$  #c(   ^ ST[7S7Xc]777[7$"3"""["8<[  T#s(^   ^ t[ S$s)^(<    &&&P$c$d:>N$$$[$$$r&r7c fd}|S)a" Run function only if hosting panel is installed, elsewhere raise PanelException This method is intended to be used as a decorator on AbstractPanel instance methods. :raise PanelException: :param dec_kwargs: kwargs passed to is_installed function :return: cfd}|S)z& :param fn: coroutine cK|jdis"|d|jjz|g|Ri|d{VS)Nz%s is not valid!r%)rCr __class__r)rPargskwargs dec_kwargsfns r'wrapperz;ensure_valid_panel..real_decorator..wrapperFsy$4$22z22 nn&)@@D24222622222222 2r&r%)rrrs` r'real_decoratorz*ensure_valid_panel..real_decoratorAs)  3 3 3 3 3 3r&r%)rrs` r'ensure_valid_panelr4s$      r&ceZdZdZdS)ModsecVendorsErrorz9 Raises when its impossible to get modsec vendor N)rr r!rr%r&r'rrRs Dr&rcfd}|S)zDecorator for functions on cPanel instance methods. Calls original function if _is_dns_only() returns False, otherwise throws cPanelException.c~K|r|d|g|Ri|d{VS)Nz'Method is not allowed for dnsonly panel) _is_dns_onlyr)rPrrrs r'rz forbid_dns_only..wrapper`sa      L..!JKK KR.t...v.........r&r%)rrs` r'forbid_dns_onlyrZs# ///// Nr&)rqabcrr collectionsr dataclassesrenumrpathlibrtypingr r r r defence360agent.utils.ipechor rrGENERIC_PANEL_NAMEr Exceptionr)r-r/r7rrrr%r&r'rs ##############!!!!!!,,,,,,,,,,,,<<<<<<<<   %      Y        I     $$$$$C$$$D<             r&defence360agent/subsys/panels/__pycache__/base.cpython-311.pyc0000644000000000000000000003607200000000000021214 0ustar r_jS$DddlZddlmZmZddlmZddlmZddlm Z ddl m Z ddl m Z mZmZmZddlmZmZgd Zd ZGd d e ZGd deZGddeZeGddZGddeZdZGddeZdZdS)N)ABCabstractmethod) defaultdict) dataclass)IntEnum)Path)DictListOptionalSet)APIError IPEchoAPI) 202122255380110443587993995z generic panelceZdZdZdZdZdS) UserLevelN)__name__ __module__ __qualname__ADMINRESSELER REGULAR_USERW/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/base.pyrrs EHLLLr&rceZdZdS)PanelExceptionNrr r!r%r&r'r)r)"Dr&r)ceZdZdS)InvalidTokenExceptionNr*r%r&r'r-r-&r+r&r-c8eZdZUeed<eed<eed<eed<dS) DomainDatadocrootdomaintypeusernameN)rr r!str__annotations__r%r&r'r/r/*s4 LLL KKK IIIMMMMMr&r/cfeZdZdZdZdgezdgezdgdgdddZeZgZ e e d Z e d Z e d Ze d Ze d*dZe d*dZe dZe deefdZe deeeeffdZe deeeeffdZe defdZdeeeeefffdZdefdZdefdZe dZde fdZ!e de"efdZ#e dede$fdZ%e dede&efdZ'e defd Z(e d d!d"Z)e deeeffd#Z*e d$e dd fd%Z+deeeeffd&Z,d'Z-e d(ede.e/fd)Z0d S)+ AbstractPanelzTAbstract class that provides only basic hosting panel integration functionality.MINIMAL465113)inout)rrrr)rrrr:123)tcpudpcdS)z\ Checks if hosting panel installed on the known path :return: bool: Nr%clss r' is_installedzAbstractPanel.is_installedDs  r&cL tjS#t$rYdSwxYw)zb Stub with external IP as currently only implementation for cPanel needed )r server_ipr rAs r' get_server_ipzAbstractPanel.get_server_ipMs8  &(( (   22 s  ##c KdSNr%rAs r'versionzAbstractPanel.versionXs tr&cK|jSrI)NAMErAs r'namezAbstractPanel.name\s xr&Nc KdS)zM Registers and enables Imunify360 UI plugin in hosting panel Nr%selfrMs r'enable_imunify_pluginz#AbstractPanel.enable_imunify_plugin` r&c KdS)zC UnRegisters Imunify360 UI plugin in hosting panel Nr%rOs r'disable_imunify_pluginz$AbstractPanel.disable_imunify_plugingrRr&c KdS)zP Returns domains hosted via control panel :return: list Nr%rPs r'get_user_domainszAbstractPanel.get_user_domainsn r&returnc KdS)zO Returns system users from hosting panel :return: list Nr%rVs r' get_userszAbstractPanel.get_usersvrXr&c KdS)zA Returns dict with domain to list of users pairs Nr%rVs r'get_domain_to_ownerz!AbstractPanel.get_domain_to_owner~rRr&c KdS)zA Returns dict with user to list of domains pairs Nr%rVs r'get_domains_per_userz"AbstractPanel.get_domains_per_userrRr&c KdS)z# Returns panel url Nr%)rPr3s r'panel_user_linkzAbstractPanel.panel_user_linkrRr&cNKd|d{VDS)z7 Returns dict with user to email pairs ci|]}|ddd S)rE)emaillocaler%).0users r' z2AbstractPanel.get_user_details..s2    B"--   r&N)r[rVs r'get_user_detailszAbstractPanel.get_user_detailssC   "nn........    r&cnKtt|d{VSrI)lenlistr[rVs r' users_countzAbstractPanel.users_counts84dnn........//000r&datacnd}|jdkr tj|j}|j}|j|fS)z Performs actions to distinguish endusers from admins :param protocol: _RpcServerProtocol :param data: parsed params :returns (user_type, user_name) Nr)_uidpwdgetpwuidpw_namerg)rPprotocolrnrMpws r' authenticatezAbstractPanel.authenticates= =A  hm,,B:D}d""r&ctrINotImplementedErrorrAs r'get_modsec_config_pathz$AbstractPanel.get_modsec_config_paths!!r&cdS)z( Return Conflict status Fr%rVs r'get_SMTP_conflict_statusz&AbstractPanel.get_SMTP_conflict_statuss ur&cdSrIr%rVs r'basedirszAbstractPanel.basedirss r&home_dircRt|j}|SrI)rresolveparent)rBrbase_dirs r' base_home_dirzAbstractPanel.base_home_dirs!>>))++2r&c, ||}t|}||}n#tt f$rYdSwxYwddlm}t|ddx}r|}t|dz |z S)Nr) MalwareTuneRAPID_SCAN_BASEDIR_OVERRIDEz.rapid-scan-db) rrr relative_to ValueError RuntimeError defence360agent.contracts.configrgetattrr4)rBrr resolved_hometailrrapid_scan_basedir_overrides r'get_rapid_scan_db_dirz#AbstractPanel.get_rapid_scan_db_dirs ((22H NN2244M ,,X66DDL)   44  A@@@@@*1 6+ +  & 33H8..5666sA AA#"A#cKt)z Returns registration key from panel, if possible, raise PanelException if not successful (or wrong panel key provided), or NoImplemented if method not supported by the panel. rxrAs r' retrieve_keyzAbstractPanel.retrieve_keys"!r&)rgc KdS)zD Notify a customer using the panel internal tooling Nr%)rB message_typeparamsrgs r'notifyzAbstractPanel.notify tr&c KdS)z5 :return dict with docroot to domain Nr%rVs r' list_docrootszAbstractPanel.list_docrootsrRr&myimunify_enabledc KdS)zK Switch UI panel configuration between Im360 and MyImunify Nr%)rBrs r'switch_ui_configzAbstractPanel.switch_ui_configrr&cK|d{V}tt}|D] \}}|||!|S)z Domain to docroot list mapping Patchman expects a subdomain to be listed separately from main domain :return: dict with domain to list of docroots N)rrrlitemsappend)rP doc_roots domain_pathsdoc_root domain_names r'get_domain_pathszAbstractPanel.get_domain_pathssx,,........ "4(( %.__%6%6 7 7 !Hk  % , ,X 6 6 6 6r&c K|d{V}|d{V}|d{V}|d{V}g}|D]}||}|d}|d} |dd} |dt t j} |dd} ||g} g}| D]0}||g}|||d1|||| | | | |d |S) NrdrerrElevel suspendedF)r1paths)r3rdlanguagerrrdomains) r[rr_rigetintrr$r)rP panel_usersr user_domains user_detailsusers user_namedetailsrdrerrrruser_domain_pathsrrs r'patchman_userszAbstractPanel.patchman_userss NN,,,,,,,, !2244444444 !6688888888 !2244444444 $  I"9-GG$EX&F[[2..FKKY-C)D)DEEE K77I"&&y"55G " &   $((b99!(("-!& LL )" &$"!*0     r&r3c"KtrIrx)rBr3s r'get_user_domains_detailsz&AbstractPanel.get_user_domains_details/s!###r&rI)1rr r!__doc__rLTCP_PORTS_COMMON OPEN_PORTSr) exceptionsmtp_allow_users classmethodrrCrGrJrMrQrTrWr r4r[r r]r_rarirrmdictrvrzboolr|r r~rrr rrrrrrrrlr/rr%r&r'r7r72sB D',,7--  ,++333   JI  ^[ [[[   ^    ^   ^  c   ^  4T#Y+?   ^  Dd3i,@   ^     ^  S$sCx.-@(A    131111#4####(""["$  #c(   ^ ST[7S7Xc]777[7$"3"""["8<[  T#s(^   ^ t[ S$s)^(<    &&&P$c$d:>N$$$[$$$r&r7c fd}|S)a" Run function only if hosting panel is installed, elsewhere raise PanelException This method is intended to be used as a decorator on AbstractPanel instance methods. :raise PanelException: :param dec_kwargs: kwargs passed to is_installed function :return: cfd}|S)z& :param fn: coroutine cK|jdis"|d|jjz|g|Ri|d{VS)Nz%s is not valid!r%)rCr __class__r)rPargskwargs dec_kwargsfns r'wrapperz;ensure_valid_panel..real_decorator..wrapperFsy$4$22z22 nn&)@@D24222622222222 2r&r%)rrrs` r'real_decoratorz*ensure_valid_panel..real_decoratorAs)  3 3 3 3 3 3r&r%)rrs` r'ensure_valid_panelr4s$      r&ceZdZdZdS)ModsecVendorsErrorz9 Raises when its impossible to get modsec vendor N)rr r!rr%r&r'rrRs Dr&rcfd}|S)zDecorator for functions on cPanel instance methods. Calls original function if _is_dns_only() returns False, otherwise throws cPanelException.c~K|r|d|g|Ri|d{VS)Nz'Method is not allowed for dnsonly panel) _is_dns_onlyr)rPrrrs r'rz forbid_dns_only..wrapper`sa      L..!JKK KR.t...v.........r&r%)rrs` r'forbid_dns_onlyrZs# ///// Nr&)rqabcrr collectionsr dataclassesrenumrpathlibrtypingr r r r defence360agent.utils.ipechor rrGENERIC_PANEL_NAMEr Exceptionr)r-r/r7rrrr%r&r'rs ##############!!!!!!,,,,,,,,,,,,<<<<<<<<   %      Y        I     $$$$$C$$$D<             r&defence360agent/subsys/panels/__pycache__/hosting_panel.cpython-311.opt-1.pyc0000644000000000000000000000305100000000000024062 0ustar r_jlddlmZddlmZddlmZdefdZeada deddfdZ d defd Z dS) )get_hosting_panel) AbstractPanel)importerreturnc2tjdrdSdS)zrs=<<<<<******S"!##    ]      r defence360agent/subsys/panels/__pycache__/hosting_panel.cpython-311.pyc0000644000000000000000000000305100000000000023123 0ustar r_jlddlmZddlmZddlmZdefdZeada deddfdZ d defd Z dS) )get_hosting_panel) AbstractPanel)importerreturnc2tjdrdSdS)zrs=<<<<<******S"!##    ]      r defence360agent/subsys/panels/base.py0000644000000000000000000002212300000000000014644 0ustar import pwd from abc import ABC, abstractmethod from collections import defaultdict from dataclasses import dataclass from enum import IntEnum from pathlib import Path from typing import Dict, List, Optional, Set from defence360agent.utils.ipecho import APIError, IPEchoAPI TCP_PORTS_COMMON = [ "20", "21", "22", "25", "53", "80", "110", "443", "587", "993", "995", ] GENERIC_PANEL_NAME = "generic panel" class UserLevel(IntEnum): ADMIN = 1 RESSELER = 2 REGULAR_USER = 3 class PanelException(Exception): pass class InvalidTokenException(Exception): pass @dataclass class DomainData: docroot: str domain: str type: str username: str class AbstractPanel(ABC): """Abstract class that provides only basic hosting panel integration functionality.""" NAME = "MINIMAL" OPEN_PORTS = { "tcp": { "in": ["465"] + TCP_PORTS_COMMON, "out": ["113"] + TCP_PORTS_COMMON, }, "udp": { "in": ["20", "21", "53", "443"], "out": ["20", "21", "53", "113", "123"], }, } exception = PanelException smtp_allow_users = [] # type: List[str] @classmethod @abstractmethod def is_installed(cls): """ Checks if hosting panel installed on the known path :return: bool: """ pass @classmethod def get_server_ip(cls): """ Stub with external IP as currently only implementation for cPanel needed """ try: return IPEchoAPI.server_ip() except APIError: return "" @classmethod async def version(cls): return None @classmethod async def name(cls): return cls.NAME @abstractmethod async def enable_imunify_plugin(self, name=None): """ Registers and enables Imunify360 UI plugin in hosting panel """ pass @abstractmethod async def disable_imunify_plugin(self, name=None): """ UnRegisters Imunify360 UI plugin in hosting panel """ pass @abstractmethod async def get_user_domains(self): """ Returns domains hosted via control panel :return: list """ pass @abstractmethod async def get_users(self) -> List[str]: """ Returns system users from hosting panel :return: list """ pass @abstractmethod async def get_domain_to_owner(self) -> Dict[str, List[str]]: """ Returns dict with domain to list of users pairs """ pass @abstractmethod async def get_domains_per_user(self) -> Dict[str, List[str]]: """ Returns dict with user to list of domains pairs """ pass @abstractmethod async def panel_user_link(self, username) -> str: """ Returns panel url """ pass async def get_user_details(self) -> Dict[str, Dict[str, str]]: """ Returns dict with user to email pairs """ return { user: {"email": "", "locale": ""} for user in await self.get_users() } async def users_count(self) -> int: return len(list(await self.get_users())) def authenticate(self, protocol, data: dict): """ Performs actions to distinguish endusers from admins :param protocol: _RpcServerProtocol :param data: parsed params :returns (user_type, user_name) """ name = None if protocol._uid != 0: # we can get here if a non-root web panel user visits i360 UI # To emulate it: # su -s /bin/bash -c # $'echo \'{"command":["config", "show"],"params":{}}\' # | nc -U -w1 \ # /var/run/defence360agent/non_root_simple_rpc.sock' # fakeuser pw = pwd.getpwuid(protocol._uid) name = pw.pw_name return protocol.user, name @classmethod def get_modsec_config_path(cls): raise NotImplementedError def get_SMTP_conflict_status(self) -> bool: """ Return Conflict status """ return False @abstractmethod def basedirs(self) -> Set[str]: pass @classmethod def base_home_dir(cls, home_dir: str) -> Path: base_dir = Path(home_dir).resolve().parent return base_dir @classmethod def get_rapid_scan_db_dir(cls, home_dir: str) -> Optional[str]: try: base_dir = cls.base_home_dir(home_dir) resolved_home = Path(home_dir).resolve() tail = resolved_home.relative_to(base_dir) # Symbolic link loop could cause runtime error except (ValueError, RuntimeError): return None from defence360agent.contracts.config import MalwareTune if rapid_scan_basedir_override := getattr( MalwareTune, "RAPID_SCAN_BASEDIR_OVERRIDE", None ): base_dir = rapid_scan_basedir_override return str(base_dir / ".rapid-scan-db" / tail) @classmethod async def retrieve_key(cls) -> str: """ Returns registration key from panel, if possible, raise PanelException if not successful (or wrong panel key provided), or NoImplemented if method not supported by the panel. """ raise NotImplementedError @classmethod async def notify(cls, *, message_type, params, user=None): """ Notify a customer using the panel internal tooling """ return None @abstractmethod async def list_docroots(self) -> Dict[str, str]: """ :return dict with docroot to domain """ pass @classmethod async def switch_ui_config(cls, myimunify_enabled: bool) -> None: """ Switch UI panel configuration between Im360 and MyImunify """ return None async def get_domain_paths(self) -> Dict[str, List[str]]: """ Domain to docroot list mapping Patchman expects a subdomain to be listed separately from main domain :return: dict with domain to list of docroots """ doc_roots = await self.list_docroots() domain_paths = defaultdict(list) for doc_root, domain_name in doc_roots.items(): domain_paths[domain_name].append(doc_root) return domain_paths async def patchman_users(self): panel_users = await self.get_users() domain_paths = await self.get_domain_paths() user_domains = await self.get_domains_per_user() user_details = await self.get_user_details() users = [] for user_name in panel_users: details = user_details[user_name] email = details["email"] locale = details["locale"] parent = details.get("parent", "") level = details.get("level", int(UserLevel.REGULAR_USER)) suspended = details.get("suspended", False) domains = user_domains.get(user_name, []) user_domain_paths = [] for domain_name in domains: paths = domain_paths.get(domain_name, []) user_domain_paths.append( { "domain": domain_name, "paths": paths, } ) users.append( { "username": user_name, "email": email, "language": locale, "parent": parent, "level": level, "suspended": suspended, "domains": user_domain_paths, } ) return users @classmethod async def get_user_domains_details(cls, username: str) -> list[DomainData]: raise NotImplementedError() def ensure_valid_panel(**dec_kwargs): """ Run function only if hosting panel is installed, elsewhere raise PanelException This method is intended to be used as a decorator on AbstractPanel instance methods. :raise PanelException: :param dec_kwargs: kwargs passed to is_installed function :return: """ def real_decorator(fn): """ :param fn: coroutine """ async def wrapper(self, *args, **kwargs): if not self.is_installed(**dec_kwargs): raise self.exception( "%s is not valid!" % self.__class__.__name__ ) return await fn(self, *args, **kwargs) return wrapper return real_decorator class ModsecVendorsError(Exception): """ Raises when its impossible to get modsec vendor """ pass def forbid_dns_only(fn): """Decorator for functions on cPanel instance methods. Calls original function if _is_dns_only() returns False, otherwise throws cPanelException.""" async def wrapper(self, *args, **kwargs): if self._is_dns_only(): raise self.exception("Method is not allowed for dnsonly panel") return await fn(self, *args, **kwargs) return wrapper defence360agent/subsys/panels/cpanel/0000755000000000000000000000000000000000000014622 5ustar defence360agent/subsys/panels/cpanel/__init__.py0000644000000000000000000000006000000000000016727 0ustar from .panel import cPanel __all__ = ["cPanel"] defence360agent/subsys/panels/cpanel/__pycache__/0000755000000000000000000000000000000000000017032 5ustar defence360agent/subsys/panels/cpanel/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000043200000000000024231 0ustar r_j0ddlmZdgZdS))cPanelrN)panelr__all__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/__init__.pyr s" *rdefence360agent/subsys/panels/cpanel/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000043200000000000023272 0ustar r_j0ddlmZdgZdS))cPanelrN)panelr__all__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/__init__.pyr s" *rdefence360agent/subsys/panels/cpanel/__pycache__/packages.cpython-311.opt-1.pyc0000644000000000000000000001424100000000000024253 0ustar r_j LddlZddlZddlmZddlmZddlmZmZej e Z GddeZ Gdde Zeejd d d edefdZddeefdZdededdfdZdededdfdZdeddfdZdeddfdZdS)N)List) timed_cache)WHMAPIExceptionwhmapi1ceZdZdS)PackageNotExistErrorN)__name__ __module__ __qualname__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/packages.pyrr sDr rcBeZdZdeefdZdedefdZdefdZdS)PkgInforeturncR|ddS)N_PACKAGE_EXTENSIONS)getsplitselfs r extensionszPkgInfo.extensionss#xx-r2288:::r namec.||vS)N)r)rrs r has_extensionzPkgInfo.has_extensionst((((r c|dS)Nrr rs rrz PkgInfo.names F|r N) r r r rstrrboolrrr r rrrsp;DI;;;;)#)$))))cr rZ)secondsd)maxsizerrcK td|d{V}n3#t$r&}dt|vrt|d}~wwxYwt |d}||d<|S)N getpkginfo)pkgzNo such file or directoryr&r)rrrrr)rdataeinfos rget_package_infor*s\t444444444  &#a&& 0 0&q)) )   4;  DDL Ks A !AA allcXKtd|d{V}d|dDS)Nlistpkgs)wantc,g|]}t|Sr )r).0items r z!list_packages..*s 2 2 2dGDMM 2 2 2r r&)r)r.r's r list_packagesr3(sB$/// / / / / / /D 2 2d5k 2 2 22r extension_name package_infocK|}||r6td||d{Vtd||dStd||dS)z;Removes extension from a package described by package_info. delpkgext)r_DELETE_EXTENSIONSNz$Extension %s disabled for package %sz0Extension %s was already disabled for package %srrrloggerr))r4r5rs rremove_extensionr;-s     D!!.11 d~            2ND     KK: r cK|}||s4t d||d|d{Vtd||dStd||dS)zrAdds extension to a package described by package_info. kwargs holds extra variables to set for the extension. addpkgext)rrNz#Extension %s enabled for package %sz/Extension %s was already enabled for package %s)r=r9)r4r5kwargsrs r add_extensionr??s     D  % %n 5 5    .             1>4     KK9>4r cKtd{VD]I} t||fi|d{V#t$r%td||dYFwxYwdS)z+Add given extension to all cPanel packages.Nz(Unable to add extension %s to package %sr)r3r?rr: exception)r4r>r&s radd_extension_for_allrBVs"__$$$$$$ >>v>> > > > > > > > >      :F       s/,AAcKtd{VD]K} t||d{V#t$r%td||dYHwxYwtddS)z0Remove given extension from all cPanel packages.Nz-Unable to remove extension %s from package %srzBImunify360 package extensions have been removed from all packages.)r3r;rr:rAr))r4r&s rremove_extension_from_allrDcs"__$$$$$$ ">377 7 7 7 7 7 7 7 7      ?F         KKLs1,A A )r+)loggingdatetimetypingrdefence360agent.utilsr(defence360agent.subsys.panels.cpanel.whmrr getLoggerr r:rdictr timedeltarr*r3r;r?rBrDr r rrMs------MMMMMMMM  8 $ $     ?   d  X  + + +S999     :9 33tG}3333 3g$$'. .  $     C D      r defence360agent/subsys/panels/cpanel/__pycache__/packages.cpython-311.pyc0000644000000000000000000001424100000000000023314 0ustar r_j LddlZddlZddlmZddlmZddlmZmZej e Z GddeZ Gdde Zeejd d d edefdZddeefdZdededdfdZdededdfdZdeddfdZdeddfdZdS)N)List) timed_cache)WHMAPIExceptionwhmapi1ceZdZdS)PackageNotExistErrorN)__name__ __module__ __qualname__b/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/packages.pyrr sDr rcBeZdZdeefdZdedefdZdefdZdS)PkgInforeturncR|ddS)N_PACKAGE_EXTENSIONS)getsplitselfs r extensionszPkgInfo.extensionss#xx-r2288:::r namec.||vS)N)r)rrs r has_extensionzPkgInfo.has_extensionst((((r c|dS)Nrr rs rrz PkgInfo.names F|r N) r r r rstrrboolrrr r rrrsp;DI;;;;)#)$))))cr rZ)secondsd)maxsizerrcK td|d{V}n3#t$r&}dt|vrt|d}~wwxYwt |d}||d<|S)N getpkginfo)pkgzNo such file or directoryr&r)rrrrr)rdataeinfos rget_package_infor*s\t444444444  &#a&& 0 0&q)) )   4;  DDL Ks A !AA allcXKtd|d{V}d|dDS)Nlistpkgs)wantc,g|]}t|Sr )r).0items r z!list_packages..*s 2 2 2dGDMM 2 2 2r r&)r)r.r's r list_packagesr3(sB$/// / / / / / /D 2 2d5k 2 2 22r extension_name package_infocK|}||r6td||d{Vtd||dStd||dS)z;Removes extension from a package described by package_info. delpkgext)r_DELETE_EXTENSIONSNz$Extension %s disabled for package %sz0Extension %s was already disabled for package %srrrloggerr))r4r5rs rremove_extensionr;-s     D!!.11 d~            2ND     KK: r cK|}||s4t d||d|d{Vtd||dStd||dS)zrAdds extension to a package described by package_info. kwargs holds extra variables to set for the extension. addpkgext)rrNz#Extension %s enabled for package %sz/Extension %s was already enabled for package %s)r=r9)r4r5kwargsrs r add_extensionr??s     D  % %n 5 5    .             1>4     KK9>4r cKtd{VD]I} t||fi|d{V#t$r%td||dYFwxYwdS)z+Add given extension to all cPanel packages.Nz(Unable to add extension %s to package %sr)r3r?rr: exception)r4r>r&s radd_extension_for_allrBVs"__$$$$$$ >>v>> > > > > > > > >      :F       s/,AAcKtd{VD]K} t||d{V#t$r%td||dYHwxYwtddS)z0Remove given extension from all cPanel packages.Nz-Unable to remove extension %s from package %srzBImunify360 package extensions have been removed from all packages.)r3r;rr:rAr))r4r&s rremove_extension_from_allrDcs"__$$$$$$ ">377 7 7 7 7 7 7 7 7      ?F         KKLs1,A A )r+)loggingdatetimetypingrdefence360agent.utilsr(defence360agent.subsys.panels.cpanel.whmrr getLoggerr r:rdictr timedeltarr*r3r;r?rBrDr r rrMs------MMMMMMMM  8 $ $     ?   d  X  + + +S999     :9 33tG}3333 3g$$'. .  $     C D      r defence360agent/subsys/panels/cpanel/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000010342600000000000023600 0ustar r_j~W`ddlZddlZddlZddlZddlZddlZddlZddlZddlm Z m Z ddl m Z ddl mZddlmZmZmZddlmZddlmZddlmZdd lmZdd lmZmZmZmZm Z dd l!m"Z"dd l#m$Z$d dl%m&Z&d dl&m'Z'm(Z(ddl%m)Z)ddl*m+Z+m,Z,edZ-edZ.eej/j0dz Z1dZ2dZ3dZ4dZ5ej6re4ne5Z7dZ8dZ9dZ:ej;e<Z=dZ>e&j?dgzZ@dZAd ZBiid!ZCGd"d#e&jDZEGd$d%e$ZFGd&d'e&jGZHdS)(N) OrderedDict defaultdict)suppress)Path)DictListSet)urlparse)Versionis_cpanel_installed)config) CheckRunErrorantivirus_modeasync_lru_cache check_runrun) IPEchoAPI)KWConfig)base) DomainDataforbid_dns_only)packages)WHMAPIExceptionwhmapi1z/var/cpanel/packages/extensionsz/usr/local/cpanelzcpanel/packages/extensionsz/etc/userplansz6/etc/userdatadomains;/var/cpanel/userdata/{user}/cachezimunify-antivirus imunify360z(/usr/local/cpanel/scripts/install_pluginz*/usr/local/cpanel/scripts/uninstall_pluginz!/etc/sysconfig/imunify360/cpanel/z,/etc/sysconfig/imunify360/cpanel/{name}.confz 2086-2087z/homez/etc/wwwacct.conf) userplansuserdatadomainsceZdZdS)cPanelExceptionN)__name__ __module__ __qualname___/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/panel.pyr"r"?sDr'r"ceZdZdZdZeZdS) AccountConfigz ^{}\s+(.*)?$z{} {}N)r#r$r%SEARCH_PATTERN WRITE_PATTERN WWWACT_CONFDEFAULT_FILENAMEr&r'r(r*r*Cs $NM"r'r*ceZdZdZgdezgdezdgdgdddZeZdgZdZ d Z e d Z e d Ze d Ze d Zejd4dZejd4dZedZe edfdeefdZdedeefdZdeefdZdeeeeffdZ dZ!e"ddefdZ#de$ddfdZ%ded eddfd!Z&ded eddfd"Z'deeeeefffd#Z(e d$Z)e d%Z*e edfd&Z+e d5d'Z,e d(Z-e d)Z.e d*eddfd+Z/e d,efd-Z0e d.Z1de2efd/Z3e dd0d1Z4deeeffd2Z5deeeeffd3Z6dS)6cPanel)143465z 2077-2080z 2082-208320952096) 37431138732073208921952703627724441)inout)202153443)rArBrCr7123r8r=r>)tcpudpcpanelz/var/cpanel/users.cache/z/var/cpanel/resellersc@tjdS)Nz/var/cpanel/dnsonly)ospathisfiler&r'r( _is_dns_onlyzcPanel._is_dns_onlygsw~~3444r'cd}tj|stjSt |5}|cdddS#1swxYwYdS)Nz/var/cpanel/mainip)rJrKexistsrget_ipopenreadstrip)clsip_conffs r( get_server_ipzcPanel.get_server_ipks&w~~g&& &#%% % ']] $a6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s&A77A;>A;ctSNr )rTs r( is_installedzcPanel.is_installedts"$$$r'cKtddgd{V\}}}|}|r|dndS)Nz/usr/local/cpanel/cpanelz-Vrunknown)rdecodesplit)rT_dataversions r(razcPanel.versionxs] :DABBBBBBBB 4++--%%''$3wqzz)3r'NcK|pt}|ttfvrtd|t|}|dz}t j|rtj ||t| d{Vtdkrtdddd|gd{Vtjd td |gd{VdS) Nz/Refusing to enable plugin: invalid plugin_name namez.rpmnewz65.0z/bin/sedz-iz-ezs@^target=.*@target=_self@gzcPanel: register_appconfig... z(/usr/local/cpanel/bin/register_appconfig) PLUGIN_NAMEAV_PLUGIN_NAMEIM360_PLUGIN_NAMEr"CONFIG_FILE_TEMPLATEformatrJrKrOshutilmover rarsysstdoutwrite)selfrd plugin_nameconfig_filenamenew_confs r(enable_imunify_pluginzcPanel.enable_imunify_plugin~s^)k ~/@A A A!/;! /55;5GG"Y. 7>>( # # 3 K/ 2 2 2 '''''' ( (76?? : :1#         :;;;:            r'cK|pt}|ttfvrtd|t|}d}t j|sxt d|dt j tdt|d5}|d dddn #1swxYwYd}tjd t#d |gd{V|rL t j|dS#t&$r(}td |Yd}~dSd}~wwxYwdS) Nz0Refusing to disable plugin: invalid plugin_name rcFzWarning: cpanel z6.conf missing, creating temporary config for uninstallT)exist_okwz!# Temporary config for uninstall z cPanel: unregister_appconfig... z*/usr/local/cpanel/bin/unregister_appconfigz#Failed to remove temporary config: )rerfrgr"rhrirJrKrOloggerinfomakedirs CONFIG_PATHrQrnrlstderrrremove Exceptionerror)rorppluginrqconfig_createdrVes r(disable_imunify_pluginzcPanel.disable_imunify_plugins+  .*;< < <!/6  /5565BBw~~o.. " KK::::    K d 3 3 3 3os++ >q<=== > > > > > > > > > > > > > > >!N <===<            H H /***** H H H F1FFGGGGGGGGG H H Hs*3CCCD11 E#;EE#cTKfdd{VDS)zD :return: list: domains hosted on server via cpanel cJg|]}|D]\}}| Sr&) _userdomains).0userdomain user_pathros r( z+cPanel.get_user_domains..sR   %)%6%6t%<%<  "      r'N) get_usersros`r(get_user_domainszcPanel.get_user_domainssN     "nn........    r'TreturncNKgfd}||||S)Nc|d}|krdS|d}|d}t|||dS)Nrr)docrootrtypeusername)appendr)rKd domain_datauser_doc_typerdomainsrs r(parserz/cPanel.get_user_domains_details..parsersiNE  "1~H!!nG NN#AHx     r'quiet)_parse_userdatadomains)rTr_pathrrrs ` @r(get_user_domains_detailszcPanel.get_user_domains_detailssO       ""5&">>>r'userplans_pathcKtj|sgStddd}|tj|krtddSt |dd5}g}|D]}|ds~|d d kret| d kr@| | d d  dddn #1swxYwYtj|tdd<|tdd<|S) Nrmtimeruserszutf-8surrogateescape)encodingerrors#:r) rJrKrL_CACHEgetgetmtimerQ startswithcountlenrSrr^)ror _cached_mtimerVrlines r( _do_get_userszcPanel._do_get_userssw~~n-- I{+//;; BG,,^<< < <+&w/ /  W5F   = E = =,,= 31,,DJJLL))A--LLC!3!9!9!;!;<<<  = = = = = = = = = = = = = = = =(*w'7'7'G'G{G$',{G$ s BD55D9<D9cFK|td{VSrY)rCPANEL_USERPLANS_PATHrs r(rzcPanel.get_userss/''(=>>>>>>>>>r'cKtt}|d{VD]8}||D] \}}|||!9|S)zp Returns dict with domain to list of users pairs :return: dict domain to list of users: Nrlistrrr)rodomain_to_usersrrr_s r(get_domain_to_ownerzcPanel.get_domain_to_owners &d++..******** 5 5D!..t44 5 5 '..t4444 5r'cKtt}|d{VD]8}||D] \}}|||!9|S)zp Returns dict with users to list of domains pairs :return: dict user to list of domains Nr)rouser_to_domainsrrr_s r(get_domains_per_userzcPanel.get_domains_per_user s &d++..******** 5 5D!..t44 5 5 %,,V4444 5r')maxsizecKtd|dd{Vd}t|dkrdSt|}|jd|jd S) z8 Returns panel url :return: str create_user_sessioncpaneld)rserviceNurlrz://z:/cpsess0000000000/frontend/jupiter/imunify/imunify.live.pl)rrr schemenetloc)rorlinkparseds r(panel_user_linkzcPanel.panel_user_links%Hi         t99>>2$-mmFMmmmmr'myimunify_enabledcKtjrdStdsdS|rdnd}|rdnd}t jdD]z}t|rWt j|}| ||d{V| ||d{V{dS)zK Switch UI panel configuration between Im360 and MyImunify Nz./var/imunify360/i360-userside-plugin.installedmyimunify_confconfz!/usr/local/cpanel/base/frontend/*) renabledrrOglobis_dirrJrKbasenamedisable_config enable_config)rorconfig_to_enableconfig_to_disable theme_path theme_names r(switch_ui_configzcPanel.switch_ui_config%s  ! 4DEELLNN 4/@L++f&7MFF=M)$GHH G GJJ&&(( GW--j99 ))*;ZHHHHHHHHH(()9:FFFFFFFFF  G Gr'rthemecK ttt|d|gd{VdS#t$r'}td||Yd}~dSd}~wwxYw)N--themez!Error in enabling config '%s': %s)rPLUGIN_INSTALL_SCRIPTrzrrwwarningrorrrs r(rzcPanel.enable_config8s K)",F,,           K K K NN> J J J J J J J J J K'- AAAcK ttt|d|gd{VdS#t$r'}td||Yd}~dSd}~wwxYw)Nrz"Error in disabling config '%s': %s)rPLUGIN_UNINSTALL_SCRIPTrzrrwrrs r(rzcPanel.disable_configEs L+",F,,           L L L NN? K K K K K K K K K LrcVKi} dt|jD}n#t$rd}YnwxYw|d{VD]}t jj}|dkr td5}tj |}dddn #1swxYwY| dd}n#ttjf$rd}YnwxYwd}d} d} t jj}n tt j|j|5}tj |}dddn #1swxYwY| dd}| d d}| d d} | d dd k} n$#ttjf$r d}d}d} d} YnwxYw|r||vrt jj}||| | t+|d ||<|S)zB Returns dict with user to email and locale pairs cFh|]}|dddS)rrr)r^)rrs r( z*cPanel.get_user_details..[s; 3""1%r'Nrootz/etc/wwwacct.conf.cache CONTACTEMAILrenFLOCALEOWNER SUSPENDED1)emaillocaleparent suspendedlevel)rRESELLERS_INFO read_text splitlinesr}rr UserLevel REGULAR_USERrQjsonloadrFileNotFoundErrorJSONDecodeErrorADMINrJrKjoin USER_INFO_DIRRESSELERint) ro user_details resellersrrrV user_inforrrrs r(get_user_detailszcPanel.get_user_detailsRs    !455??AALLNNII   III ..********' ' DN/Ev~~7881A$(IaLL 111111111111111%MM."==EE)4+?@EEE! , &bgll4+=tDDEE1$(IaLL 111111111111111%MM."==E&]]8R88F&]]7B77F ) k2 > ># EII)4+?@&&&EFF %III & 4!2!2!N3E  &U ""L  sAA AAC"C4 C"C C"C C""C=<C=2G E, G,E0 0G3E0 4AGG54G5cg}d|vr@tjtjj}|d|}|d}|D]S}tj|r2| tj |T|rt|ndS)z^checks mtime of userdatadomains files (including cache) returns max mtime of all files{user};r) pwdgetpwuidrJgetuidpw_namereplacer^rKrOrrmax)rTr_mtimes call_as_user path_listpath_s r(_get_max_mtimezcPanel._get_max_mtimes  u  < 44.parserspNE +A [^++NNA}#566666&&='9::::: r'r)rrr )rrrrritemsr)rTrrr cached_datarrrs ` @@r(rzcPanel._userdomainss))&%88  " !mm -- ; ; ; ; ; ; ; ""5&">>>{###''..}}- -  !&)}}r'cd|vr@tjtjj}|d|}|d}|D]i} t|d}n5#t$r(}|st d||Yd}~Bd}~wwxYw t|D]\}} | } n,#t$rt d||YCwxYw| s\| ddkr|st d||| d\} } | d } ||| |  |R#|wxYwdS) NrrrbzCan't open file %s [%s]z-Broken %s line in file "%s"; line was ignoredz: rz2Can't parse %s line in file '%s'; line was ignoredz==)rrrJr r r r^rQr}rwr enumerater]UnicodeDecodeErrorrSrclose) rrrrrrfile_rirrdomain_raw_datars r(rzcPanel._parse_userdatadomainss u  < 44.>+FO"1"7"7"9"9"?"?"E"EKF5&+6666/72  I$ $ sI A11 B#;BB#'F.<CF.&C:7F.9C::BF..Gc4td|DS)Nc3nK|]0}t|V1dSrY)CPANEL_PACKAGE_EXTENSIONS_PATHjoinpathis_file)rfiles r( z0cPanel.is_extension_installed..sP   + 3 3D 9 9 A A C C      r')all)rTpkgss r(is_extension_installedzcPanel.is_extension_installeds0        r'cvK tdd{V}td|dD}dD]dtfd|dD}td|dD}td |d Dsd Sen#ttf$rYd SwxYwd S) N list_hooksc32K|]}|ddk|VdS)categoryWhostmgrNr&)rcats r(r+z+cPanel.is_hook_installed..s=z?j000000r' categories)zAccounts::change_packagezAccounts::CreatezAccounts::Modifyc34K|]}|dk|VdS)eventNr&)rev event_names r(r+z+cPanel.is_hook_installed..s>'{j000000r'eventsc32K|]}|ddk|VdS)stagepostNr&)rsts r(r+z+cPanel.is_hook_installed.. s9BwK64I4IB4I4I4I4Ir'stagesc3.K|]}|ddkVdS)hookzImunifyHook::hook_processingNr&)ractions r(r+z+cPanel.is_hook_installed..s@6N&DDr'actionsFT)rnextany StopIterationr)rThooksr2r7r<r9s @r(is_hook_installedzcPanel.is_hook_installedsQ !,////////E .H  ! ! &x0 !&x"' "2!!55 ! !$/   55 tsBB!B!!B65B6extention_namecKtddd|D]$}tjt|z t%t j|fi|d{Vtjtj j ddtjtdz ttgdd{VdS)NiT)modeparentsru)rKruImunifyHook.pm)"/usr/local/cpanel/bin/manage_hooksaddmodule ImunifyHook)r'mkdirrjcopy2"PREINSTALL_PACKAGE_EXTENSIONS_PATHradd_extension_for_allrJryrCoreINBOX_HOOKS_DIRCPANEL_HOOKS_PATHr)rTrIextention_fileskwargsfilenames r(install_extensionzcPanel.install_extensions ',,t -   (  H L2X=.     ,^FFvFFFFFFFFF FK/edKKKK .1A A                  r'extension_namecKtgdd{Vtt5tdz dddn #1swxYwYt j|d{V|D]J}tt5t|z dddn #1swxYwYKdS)N)rNdelrPrQrM)rrrrXunlinkrremove_extension_from_allr')rTr]rYr[s r(uninstall_extensionzcPanel.uninstall_extension=s            ' ( ( < < !1 1 9 9 ; ; ; < < < < < < < < < < < < < < <0@@@@@@@@@' E EH+,, E E/(:BBDDD E E E E E E E E E E E E E E E E Es#AAAB==C C cg}tdd5}|D]V}|}t|dkr||dW dddn #1swxYwY|S)Nz /proc/mountsrr)rQrSr^rr)mountsrVrvaluess r(rez cPanel.mountsRs .# & & -! - -++--v;;??MM&),,, - - - - - - - - - - - - - - - -  sAA::A>A>cDtd}td}|tn|}t|h}||S|D]0}||vr*|ds||1|S)aeFetch list of basedirs. On cPanel, basedir is configured as HOMEDIR variable in /etc/wwwacct.conf. Also, there is a way to specify additional mount points as containing user folders, through HOMEMATCH variable. If value from HOMEMATCH variable is contained within a mount point path, cPanel uses this directory too.HOMEDIR HOMEMATCHNz /home/virtfs/)r*rBASE_DIRrerrO)rohomedir homematchbasedirsmounts r(rmzcPanel.basedirs\s **..00!+..2244 %o((7g&  O[[]] $ $EE!!%*:*:?*K*K! U###r')rc|KtjjsdStj|sdS|||d}t|jd|d}tj|}t|g| d{V}tj | d S) zG Notify a customer using cPanel iContact Notifications F)r) message_typeparamsrz .notify(%s)zB/usr/local/cpanel/whostmgr/docroot/cgi/imunify/handlers/notify.cgi)inputNr)r) r AdminContactsENABLE_ICONTACT_NOTIFICATIONSshould_send_user_notificationsrwrxr#rdumpsrencodeloadsr])rTrprqrr`cmdstdinr@s r(notifyz cPanel.notifyrs #A 54dCCC 5 ,MM s|000$777 +  4  se5<<>>:::::::::z#**,=*>>???r'clKtfd}|t|dS)Nc*|d|d<dS)Nrrr&)rKrrresults r(rz$cPanel.list_docroots..parsers%0^F;q> " " "r'Tr)dictrCPANEL_USERDATADOMAINS_PATHrorr~s @r( list_docrootszcPanel.list_docrootssV 4 4 4 4 4 ## 't $    r'cTKifd}|t|dS)Nc |dg|<dS)Nrr&)r_rrr~s r(rz'cPanel.get_domain_paths..parsers$Q(F1IIIr'Tr)rrrs @r(get_domain_pathszcPanel.get_domain_pathssR ) ) ) ) ) ## 't $    r'rY)T)7r#r$r%NAMETCP_PORTS_CPANEL OPEN_PORTSr" exceptionsmtp_allow_usersrr staticmethodrM classmethodrWrZrarensure_valid_panelrsrrrrrrrstrrrrrrrrrboolrrrrrrrrr.rHr\rbrer rmr{rrr&r'r(r0r0Is DKJJ      $,++KKK  %J. I z.M,N55\5$$[$%%[%44[4 T    >T"H"H"H"HH  _ 8 j [(#$s),? c???? 4T#Y+?       _S!!!nnnn"!n GGGGGG& K# Kc Kd K K K K L3 Ls Lt L L L L9S$sCx.-@(A9999v . .[ . L L[ L6d[8)))\)V  [ [@        [  DEsEEE[E(\#c(,8<@@@@[@, T#s(^     S$s)^(<      r'r0)IrrloggingrJos.pathrrjrl collectionsrr contextlibrpathlibrtypingrrr urllib.parser packaging.versionr 3defence360agent.application.determine_hosting_panelr defence360agent.contractsrdefence360agent.utilsrrrrrdefence360agent.utils.ipechordefence360agent.utils.kwconfigrrrrrrwhmrrr'rX PackagingDATADIRrTrrrfrgrrerrrz getLoggerr#rwrhTCP_PORTS_COMMONrrjr-rPanelExceptionr"r* AbstractPanelr0r&r'r(rs   00000000""""""""""!!!!!!%%%%%%-,,,,,322222333333........))))))))!%&G!H!HD,--D  !""%AA#)<%  . 6Mnne&j?dgzZ@dZAd ZBiid!ZCGd"d#e&jDZEGd$d%e$ZFGd&d'e&jGZHdS)(N) OrderedDict defaultdict)suppress)Path)DictListSet)urlparse)Versionis_cpanel_installed)config) CheckRunErrorantivirus_modeasync_lru_cache check_runrun) IPEchoAPI)KWConfig)base) DomainDataforbid_dns_only)packages)WHMAPIExceptionwhmapi1z/var/cpanel/packages/extensionsz/usr/local/cpanelzcpanel/packages/extensionsz/etc/userplansz6/etc/userdatadomains;/var/cpanel/userdata/{user}/cachezimunify-antivirus imunify360z(/usr/local/cpanel/scripts/install_pluginz*/usr/local/cpanel/scripts/uninstall_pluginz!/etc/sysconfig/imunify360/cpanel/z,/etc/sysconfig/imunify360/cpanel/{name}.confz 2086-2087z/homez/etc/wwwacct.conf) userplansuserdatadomainsceZdZdS)cPanelExceptionN)__name__ __module__ __qualname___/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/panel.pyr"r"?sDr'r"ceZdZdZdZeZdS) AccountConfigz ^{}\s+(.*)?$z{} {}N)r#r$r%SEARCH_PATTERN WRITE_PATTERN WWWACT_CONFDEFAULT_FILENAMEr&r'r(r*r*Cs $NM"r'r*ceZdZdZgdezgdezdgdgdddZeZdgZdZ d Z e d Z e d Ze d Ze d Zejd4dZejd4dZedZe edfdeefdZdedeefdZdeefdZdeeeeffdZ dZ!e"ddefdZ#de$ddfdZ%ded eddfd!Z&ded eddfd"Z'deeeeefffd#Z(e d$Z)e d%Z*e edfd&Z+e d5d'Z,e d(Z-e d)Z.e d*eddfd+Z/e d,efd-Z0e d.Z1de2efd/Z3e dd0d1Z4deeeffd2Z5deeeeffd3Z6dS)6cPanel)143465z 2077-2080z 2082-208320952096) 37431138732073208921952703627724441)inout)202153443)rArBrCr7123r8r=r>)tcpudpcpanelz/var/cpanel/users.cache/z/var/cpanel/resellersc@tjdS)Nz/var/cpanel/dnsonly)ospathisfiler&r'r( _is_dns_onlyzcPanel._is_dns_onlygsw~~3444r'cd}tj|stjSt |5}|cdddS#1swxYwYdS)Nz/var/cpanel/mainip)rJrKexistsrget_ipopenreadstrip)clsip_conffs r( get_server_ipzcPanel.get_server_ipks&w~~g&& &#%% % ']] $a6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $s&A77A;>A;ctSNr )rTs r( is_installedzcPanel.is_installedts"$$$r'cKtddgd{V\}}}|}|r|dndS)Nz/usr/local/cpanel/cpanelz-Vrunknown)rdecodesplit)rT_dataversions r(razcPanel.versionxs] :DABBBBBBBB 4++--%%''$3wqzz)3r'NcK|pt}|ttfvrtd|t|}|dz}t j|rtj ||t| d{Vtdkrtdddd|gd{Vtjd td |gd{VdS) Nz/Refusing to enable plugin: invalid plugin_name namez.rpmnewz65.0z/bin/sedz-iz-ezs@^target=.*@target=_self@gzcPanel: register_appconfig... z(/usr/local/cpanel/bin/register_appconfig) PLUGIN_NAMEAV_PLUGIN_NAMEIM360_PLUGIN_NAMEr"CONFIG_FILE_TEMPLATEformatrJrKrOshutilmover rarsysstdoutwrite)selfrd plugin_nameconfig_filenamenew_confs r(enable_imunify_pluginzcPanel.enable_imunify_plugin~s^)k ~/@A A A!/;! /55;5GG"Y. 7>>( # # 3 K/ 2 2 2 '''''' ( (76?? : :1#         :;;;:            r'cK|pt}|ttfvrtd|t|}d}t j|sxt d|dt j tdt|d5}|d dddn #1swxYwYd}tjd t#d |gd{V|rL t j|dS#t&$r(}td |Yd}~dSd}~wwxYwdS) Nz0Refusing to disable plugin: invalid plugin_name rcFzWarning: cpanel z6.conf missing, creating temporary config for uninstallT)exist_okwz!# Temporary config for uninstall z cPanel: unregister_appconfig... z*/usr/local/cpanel/bin/unregister_appconfigz#Failed to remove temporary config: )rerfrgr"rhrirJrKrOloggerinfomakedirs CONFIG_PATHrQrnrlstderrrremove Exceptionerror)rorppluginrqconfig_createdrVes r(disable_imunify_pluginzcPanel.disable_imunify_plugins+  .*;< < <!/6  /5565BBw~~o.. " KK::::    K d 3 3 3 3os++ >q<=== > > > > > > > > > > > > > > >!N <===<            H H /***** H H H F1FFGGGGGGGGG H H Hs*3CCCD11 E#;EE#cTKfdd{VDS)zD :return: list: domains hosted on server via cpanel cJg|]}|D]\}}| Sr&) _userdomains).0userdomain user_pathros r( z+cPanel.get_user_domains..sR   %)%6%6t%<%<  "      r'N) get_usersros`r(get_user_domainszcPanel.get_user_domainssN     "nn........    r'TreturncNKgfd}||||S)Nc|d}|krdS|d}|d}t|||dS)Nrr)docrootrtypeusername)appendr)rKd domain_datauser_doc_typerdomainsrs r(parserz/cPanel.get_user_domains_details..parsersiNE  "1~H!!nG NN#AHx     r'quiet)_parse_userdatadomains)rTr_pathrrrs ` @r(get_user_domains_detailszcPanel.get_user_domains_detailssO       ""5&">>>r'userplans_pathcKtj|sgStddd}|tj|krtddSt |dd5}g}|D]}|ds~|d d kret| d kr@| | d d  dddn #1swxYwYtj|tdd<|tdd<|S) Nrmtimeruserszutf-8surrogateescape)encodingerrors#:r) rJrKrL_CACHEgetgetmtimerQ startswithcountlenrSrr^)ror _cached_mtimerVrlines r( _do_get_userszcPanel._do_get_userssw~~n-- I{+//;; BG,,^<< < <+&w/ /  W5F   = E = =,,= 31,,DJJLL))A--LLC!3!9!9!;!;<<<  = = = = = = = = = = = = = = = =(*w'7'7'G'G{G$',{G$ s BD55D9<D9cFK|td{VSrY)rCPANEL_USERPLANS_PATHrs r(rzcPanel.get_userss/''(=>>>>>>>>>r'cKtt}|d{VD]8}||D] \}}|||!9|S)zp Returns dict with domain to list of users pairs :return: dict domain to list of users: Nrlistrrr)rodomain_to_usersrrr_s r(get_domain_to_ownerzcPanel.get_domain_to_owners &d++..******** 5 5D!..t44 5 5 '..t4444 5r'cKtt}|d{VD]8}||D] \}}|||!9|S)zp Returns dict with users to list of domains pairs :return: dict user to list of domains Nr)rouser_to_domainsrrr_s r(get_domains_per_userzcPanel.get_domains_per_user s &d++..******** 5 5D!..t44 5 5 %,,V4444 5r')maxsizecKtd|dd{Vd}t|dkrdSt|}|jd|jd S) z8 Returns panel url :return: str create_user_sessioncpaneld)rserviceNurlrz://z:/cpsess0000000000/frontend/jupiter/imunify/imunify.live.pl)rrr schemenetloc)rorlinkparseds r(panel_user_linkzcPanel.panel_user_links%Hi         t99>>2$-mmFMmmmmr'myimunify_enabledcKtjrdStdsdS|rdnd}|rdnd}t jdD]z}t|rWt j|}| ||d{V| ||d{V{dS)zK Switch UI panel configuration between Im360 and MyImunify Nz./var/imunify360/i360-userside-plugin.installedmyimunify_confconfz!/usr/local/cpanel/base/frontend/*) renabledrrOglobis_dirrJrKbasenamedisable_config enable_config)rorconfig_to_enableconfig_to_disable theme_path theme_names r(switch_ui_configzcPanel.switch_ui_config%s  ! 4DEELLNN 4/@L++f&7MFF=M)$GHH G GJJ&&(( GW--j99 ))*;ZHHHHHHHHH(()9:FFFFFFFFF  G Gr'rthemecK ttt|d|gd{VdS#t$r'}td||Yd}~dSd}~wwxYw)N--themez!Error in enabling config '%s': %s)rPLUGIN_INSTALL_SCRIPTrzrrwwarningrorrrs r(rzcPanel.enable_config8s K)",F,,           K K K NN> J J J J J J J J J K'- AAAcK ttt|d|gd{VdS#t$r'}td||Yd}~dSd}~wwxYw)Nrz"Error in disabling config '%s': %s)rPLUGIN_UNINSTALL_SCRIPTrzrrwrrs r(rzcPanel.disable_configEs L+",F,,           L L L NN? K K K K K K K K K LrcVKi} dt|jD}n#t$rd}YnwxYw|d{VD]}t jj}|dkr td5}tj |}dddn #1swxYwY| dd}n#ttjf$rd}YnwxYwd}d} d} t jj}n tt j|j|5}tj |}dddn #1swxYwY| dd}| d d}| d d} | d dd k} n$#ttjf$r d}d}d} d} YnwxYw|r||vrt jj}||| | t+|d ||<|S)zB Returns dict with user to email and locale pairs cFh|]}|dddS)rrr)r^)rrs r( z*cPanel.get_user_details..[s; 3""1%r'Nrootz/etc/wwwacct.conf.cache CONTACTEMAILrenFLOCALEOWNER SUSPENDED1)emaillocaleparent suspendedlevel)rRESELLERS_INFO read_text splitlinesr}rr UserLevel REGULAR_USERrQjsonloadrFileNotFoundErrorJSONDecodeErrorADMINrJrKjoin USER_INFO_DIRRESSELERint) ro user_details resellersrrrV user_inforrrrs r(get_user_detailszcPanel.get_user_detailsRs    !455??AALLNNII   III ..********' ' DN/Ev~~7881A$(IaLL 111111111111111%MM."==EE)4+?@EEE! , &bgll4+=tDDEE1$(IaLL 111111111111111%MM."==E&]]8R88F&]]7B77F ) k2 > ># EII)4+?@&&&EFF %III & 4!2!2!N3E  &U ""L  sAA AAC"C4 C"C C"C C""C=<C=2G E, G,E0 0G3E0 4AGG54G5cg}d|vr@tjtjj}|d|}|d}|D]S}tj|r2| tj |T|rt|ndS)z^checks mtime of userdatadomains files (including cache) returns max mtime of all files{user};r) pwdgetpwuidrJgetuidpw_namereplacer^rKrOrrmax)rTr_mtimes call_as_user path_listpath_s r(_get_max_mtimezcPanel._get_max_mtimes  u  < 44.parserspNE +A [^++NNA}#566666&&='9::::: r'r)rrr )rrrrritemsr)rTrrr cached_datarrrs ` @@r(rzcPanel._userdomainss))&%88  " !mm -- ; ; ; ; ; ; ; ""5&">>>{###''..}}- -  !&)}}r'cd|vr@tjtjj}|d|}|d}|D]i} t|d}n5#t$r(}|st d||Yd}~Bd}~wwxYw t|D]\}} | } n,#t$rt d||YCwxYw| s\| ddkr|st d||| d\} } | d } ||| |  |R#|wxYwdS) NrrrbzCan't open file %s [%s]z-Broken %s line in file "%s"; line was ignoredz: rz2Can't parse %s line in file '%s'; line was ignoredz==)rrrJr r r r^rQr}rwr enumerater]UnicodeDecodeErrorrSrclose) rrrrrrfile_rirrdomain_raw_datars r(rzcPanel._parse_userdatadomainss u  < 44.>+FO"1"7"7"9"9"?"?"E"EKF5&+6666/72  I$ $ sI A11 B#;BB#'F.<CF.&C:7F.9C::BF..Gc4td|DS)Nc3nK|]0}t|V1dSrY)CPANEL_PACKAGE_EXTENSIONS_PATHjoinpathis_file)rfiles r( z0cPanel.is_extension_installed..sP   + 3 3D 9 9 A A C C      r')all)rTpkgss r(is_extension_installedzcPanel.is_extension_installeds0        r'cvK tdd{V}td|dD}dD]dtfd|dD}td|dD}td |d Dsd Sen#ttf$rYd SwxYwd S) N list_hooksc32K|]}|ddk|VdS)categoryWhostmgrNr&)rcats r(r+z+cPanel.is_hook_installed..s=z?j000000r' categories)zAccounts::change_packagezAccounts::CreatezAccounts::Modifyc34K|]}|dk|VdS)eventNr&)rev event_names r(r+z+cPanel.is_hook_installed..s>'{j000000r'eventsc32K|]}|ddk|VdS)stagepostNr&)rsts r(r+z+cPanel.is_hook_installed.. s9BwK64I4IB4I4I4I4Ir'stagesc3.K|]}|ddkVdS)hookzImunifyHook::hook_processingNr&)ractions r(r+z+cPanel.is_hook_installed..s@6N&DDr'actionsFT)rnextany StopIterationr)rThooksr2r7r<r9s @r(is_hook_installedzcPanel.is_hook_installedsQ !,////////E .H  ! ! &x0 !&x"' "2!!55 ! !$/   55 tsBB!B!!B65B6extention_namecKtddd|D]$}tjt|z t%t j|fi|d{Vtjtj j ddtjtdz ttgdd{VdS)NiT)modeparentsru)rKruImunifyHook.pm)"/usr/local/cpanel/bin/manage_hooksaddmodule ImunifyHook)r'mkdirrjcopy2"PREINSTALL_PACKAGE_EXTENSIONS_PATHradd_extension_for_allrJryrCoreINBOX_HOOKS_DIRCPANEL_HOOKS_PATHr)rTrIextention_fileskwargsfilenames r(install_extensionzcPanel.install_extensions ',,t -   (  H L2X=.     ,^FFvFFFFFFFFF FK/edKKKK .1A A                  r'extension_namecKtgdd{Vtt5tdz dddn #1swxYwYt j|d{V|D]J}tt5t|z dddn #1swxYwYKdS)N)rNdelrPrQrM)rrrrXunlinkrremove_extension_from_allr')rTr]rYr[s r(uninstall_extensionzcPanel.uninstall_extension=s            ' ( ( < < !1 1 9 9 ; ; ; < < < < < < < < < < < < < < <0@@@@@@@@@' E EH+,, E E/(:BBDDD E E E E E E E E E E E E E E E E Es#AAAB==C C cg}tdd5}|D]V}|}t|dkr||dW dddn #1swxYwY|S)Nz /proc/mountsrr)rQrSr^rr)mountsrVrvaluess r(rez cPanel.mountsRs .# & & -! - -++--v;;??MM&),,, - - - - - - - - - - - - - - - -  sAA::A>A>cDtd}td}|tn|}t|h}||S|D]0}||vr*|ds||1|S)aeFetch list of basedirs. On cPanel, basedir is configured as HOMEDIR variable in /etc/wwwacct.conf. Also, there is a way to specify additional mount points as containing user folders, through HOMEMATCH variable. If value from HOMEMATCH variable is contained within a mount point path, cPanel uses this directory too.HOMEDIR HOMEMATCHNz /home/virtfs/)r*rBASE_DIRrerrO)rohomedir homematchbasedirsmounts r(rmzcPanel.basedirs\s **..00!+..2244 %o((7g&  O[[]] $ $EE!!%*:*:?*K*K! U###r')rc|KtjjsdStj|sdS|||d}t|jd|d}tj|}t|g| d{V}tj | d S) zG Notify a customer using cPanel iContact Notifications F)r) message_typeparamsrz .notify(%s)zB/usr/local/cpanel/whostmgr/docroot/cgi/imunify/handlers/notify.cgi)inputNr)r) r AdminContactsENABLE_ICONTACT_NOTIFICATIONSshould_send_user_notificationsrwrxr#rdumpsrencodeloadsr])rTrprqrr`cmdstdinr@s r(notifyz cPanel.notifyrs #A 54dCCC 5 ,MM s|000$777 +  4  se5<<>>:::::::::z#**,=*>>???r'clKtfd}|t|dS)Nc*|d|d<dS)Nrrr&)rKrrresults r(rz$cPanel.list_docroots..parsers%0^F;q> " " "r'Tr)dictrCPANEL_USERDATADOMAINS_PATHrorr~s @r( list_docrootszcPanel.list_docrootssV 4 4 4 4 4 ## 't $    r'cTKifd}|t|dS)Nc |dg|<dS)Nrr&)r_rrr~s r(rz'cPanel.get_domain_paths..parsers$Q(F1IIIr'Tr)rrrs @r(get_domain_pathszcPanel.get_domain_pathssR ) ) ) ) ) ## 't $    r'rY)T)7r#r$r%NAMETCP_PORTS_CPANEL OPEN_PORTSr" exceptionsmtp_allow_usersrr staticmethodrM classmethodrWrZrarensure_valid_panelrsrrrrrrrstrrrrrrrrrboolrrrrrrrrr.rHr\rbrer rmr{rrr&r'r(r0r0Is DKJJ      $,++KKK  %J. I z.M,N55\5$$[$%%[%44[4 T    >T"H"H"H"HH  _ 8 j [(#$s),? c???? 4T#Y+?       _S!!!nnnn"!n GGGGGG& K# Kc Kd K K K K L3 Ls Lt L L L L9S$sCx.-@(A9999v . .[ . L L[ L6d[8)))\)V  [ [@        [  DEsEEE[E(\#c(,8<@@@@[@, T#s(^     S$s)^(<      r'r0)IrrloggingrJos.pathrrjrl collectionsrr contextlibrpathlibrtypingrrr urllib.parser packaging.versionr 3defence360agent.application.determine_hosting_panelr defence360agent.contractsrdefence360agent.utilsrrrrrdefence360agent.utils.ipechordefence360agent.utils.kwconfigrrrrrrwhmrrr'rX PackagingDATADIRrTrrrfrgrrerrrz getLoggerr#rwrhTCP_PORTS_COMMONrrjr-rPanelExceptionr"r* AbstractPanelr0r&r'r(rs   00000000""""""""""!!!!!!%%%%%%-,,,,,322222333333........))))))))!%&G!H!HD,--D  !""%AA#)<%  . 6Mnnzwhmapi1..'s0 E E EdagnnQa)) E E ErzBroken output from whmapi1: z , reason: metadataresultdatazwhmapi {} command failed: {}commandreason) statusmsgzCannot Read License FilezCannot Read CPanel License Filez/Broken output from whmapi1 (KeyError: {}): {!r})extend WHMAPI1_CMDitemsrdecoder returncodesignalSIGTERMloggerwarningrjsonloadsJSONDecodeErrorrKeyErrorr)functionrkwargscmdparams raw_outputeoutputs rwhmapi1r6$s  "6((CJJ _h7888 E Efllnn E E EF%cFl33333333;;==  !!.55:&y16*3Eh3O      4  F F NN< = = =$ $!AHHv  sP*A11 C;AB<<CCD)C??DE;E G#AF==Gctg|d} td|tj|dtj}n*#tj$r}td|z|d}~wwxYw|rtj |j }g}t|D]l\}} |} |D] } | | } | | +#t$r5}|dkrtd|| dYd}~ed}~wwxYwndSt|dkr|dS|S) Nrzsubprocess.run(%r)T)checkstdoutzFailed to run whmapi1: %srzCould not parse whmapi1 output)r#r)debug subprocessrunPIPECalledProcessErrorrr+r,r9r% enumerateappendr.len) args path_listr1resr4decoded_outputri element_pathitemkeys r run_whmapirKLs  / / /CF )3///nSZ_EEE  (FFF9A=>>AEFCJ$5$5$7$788(33 ( (OA| (%'%%C9DD d#### ( ( (66*8 MM$'''''''' (  ("  6{{aay s/rcfd}|S)NcKd} |i|d{V}nZ#t$r1}tt|Yd}~n$d}~wtdYnxYw|S)NzSomething went wrong)rr)errorstr exception)rCr0rvswwfuncs rwrapperz catch_exception..wrappers  5tT,V,,,,,,,,BB # # # LLS " " " " " " " " 5   3 4 4 4 4 4 s A-'A  A-r)rXrYs` rcatch_exceptionrZs#      Nr)F)r+loggingr<r' urllib.parserdefence360agent.utilsrr"defence360agent.subsys.panels.baser getLoggerr r)r#WHMAPI_CERT_ERROR_LISTrrr6rKrNrPrZrrrrasC  ::::::::======  8 $ $"      n           %%%%P$$$N444rdefence360agent/subsys/panels/cpanel/__pycache__/whm.cpython-311.pyc0000644000000000000000000001446400000000000022340 0ustar r_jGddlZddlZddlZddlZddlmZddlmZmZddl m Z ej e Z dZgdZGdde ZGd d eZdd Zd ZdZdZdZdS)N)quote) check_run CheckRunError)PanelExceptionz/usr/sbin/whmapi1)zno certificatezno key with the idzcannot read license filezinvalid license filezlicense file expiredceZdZdZdS)WHMAPIExceptionz5Got broken output or other problem during WHMAPI callN__name__ __module__ __qualname____doc__]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/cpanel/whm.pyrrs??DrrceZdZdZdS)WHMAPILicenseErrorz$Raises when cannot Read License FileNr rrrrrs..DrrFcK|rdgng}|td|gd|D} t||zd{V}nS#t $rF}|jtj kr)t |t||d}~wwxYw tj |}n-#tj$r}td|d||d}~wwxYw |ddr|dStd |dd |dd #t $r_}d |vr!t d t"td||d}~wwxYw)Nsudo --output=jsoncZg|](\}}d|t|)S)z{}={})formatr).0kvs r zwhmapi1..'s0 E E EdagnnQa)) E E ErzBroken output from whmapi1: z , reason: metadataresultdatazwhmapi {} command failed: {}commandreason) statusmsgzCannot Read License FilezCannot Read CPanel License Filez/Broken output from whmapi1 (KeyError: {}): {!r})extend WHMAPI1_CMDitemsrdecoder returncodesignalSIGTERMloggerwarningrjsonloadsJSONDecodeErrorrKeyErrorr)functionrkwargscmdparams raw_outputeoutputs rwhmapi1r6$s  "6((CJJ _h7888 E Efllnn E E EF%cFl33333333;;==  !!.55:&y16*3Eh3O      4  F F NN< = = =$ $!AHHv  sP*A11 C;AB<<CCD)C??DE;E G#AF==Gctg|d} td|tj|dtj}n*#tj$r}td|z|d}~wwxYw|rtj |j }g}t|D]l\}} |} |D] } | | } | | +#t$r5}|dkrtd|| dYd}~ed}~wwxYwndSt|dkr|dS|S) Nrzsubprocess.run(%r)T)checkstdoutzFailed to run whmapi1: %srzCould not parse whmapi1 output)r#r)debug subprocessrunPIPECalledProcessErrorrr+r,r9r% enumerateappendr.len) args path_listr1resr4decoded_outputri element_pathitemkeys r run_whmapirKLs  / / /CF )3///nSZ_EEE  (FFF9A=>>AEFCJ$5$5$7$788(33 ( (OA| (%'%%C9DD d#### ( ( (66*8 MM$'''''''' (  ("  6{{aay s/rcfd}|S)NcKd} |i|d{V}nZ#t$r1}tt|Yd}~n$d}~wtdYnxYw|S)NzSomething went wrong)rr)errorstr exception)rCr0rvswwfuncs rwrapperz catch_exception..wrappers  5tT,V,,,,,,,,BB # # # LLS " " " " " " " " 5   3 4 4 4 4 4 s A-'A  A-r)rXrYs` rcatch_exceptionrZs#      Nr)F)r+loggingr<r' urllib.parserdefence360agent.utilsrr"defence360agent.subsys.panels.baser getLoggerr r)r#WHMAPI_CERT_ERROR_LISTrrr6rKrNrPrZrrrrasC  ::::::::======  8 $ $"      n           %%%%P$$$N444rdefence360agent/subsys/panels/cpanel/packages.py0000644000000000000000000000637600000000000016766 0ustar import logging import datetime from typing import List from defence360agent.utils import timed_cache from defence360agent.subsys.panels.cpanel.whm import WHMAPIException, whmapi1 logger = logging.getLogger(__name__) class PackageNotExistError(WHMAPIException): pass class PkgInfo(dict): def extensions(self) -> List[str]: return self.get("_PACKAGE_EXTENSIONS", "").split() def has_extension(self, name: str) -> bool: return name in self.extensions() def name(self) -> str: return self["name"] @timed_cache(datetime.timedelta(seconds=90), maxsize=100) async def get_package_info(name: str) -> PkgInfo: try: data = await whmapi1("getpkginfo", pkg=name) except WHMAPIException as e: if "No such file or directory" in str(e): raise PackageNotExistError(e) else: raise info = PkgInfo(data["pkg"]) info["name"] = name return info async def list_packages(want="all") -> List[PkgInfo]: data = await whmapi1("listpkgs", want=want) return [PkgInfo(item) for item in data["pkg"]] async def remove_extension(extension_name: str, package_info: PkgInfo) -> None: """Removes extension from a package described by package_info.""" name = package_info.name() if package_info.has_extension(extension_name): await whmapi1( "delpkgext", name=name, _DELETE_EXTENSIONS=extension_name ) logger.info( "Extension %s disabled for package %s", extension_name, name ) return logger.info( "Extension %s was already disabled for package %s", extension_name, name, ) async def add_extension( extension_name: str, package_info: PkgInfo, **kwargs ) -> None: """Adds extension to a package described by package_info. kwargs holds extra variables to set for the extension.""" name = package_info.name() if not package_info.has_extension(extension_name): await whmapi1( "addpkgext", name=name, _PACKAGE_EXTENSIONS=extension_name, **kwargs ) logger.info( "Extension %s enabled for package %s", extension_name, name ) return logger.info( "Extension %s was already enabled for package %s", extension_name, name ) async def add_extension_for_all(extension_name: str, **kwargs) -> None: """Add given extension to all cPanel packages.""" for pkg in await list_packages(): try: await add_extension(extension_name, pkg, **kwargs) except WHMAPIException: logger.exception( "Unable to add extension %s to package %s", extension_name, pkg["name"], ) async def remove_extension_from_all(extension_name: str) -> None: """Remove given extension from all cPanel packages.""" for pkg in await list_packages(): try: await remove_extension(extension_name, pkg) except WHMAPIException: logger.exception( "Unable to remove extension %s from package %s", extension_name, pkg["name"], ) logger.info( "Imunify360 package extensions have been removed from all packages." ) defence360agent/subsys/panels/cpanel/panel.py0000644000000000000000000005357600000000000016313 0ustar import glob import json import logging import os import os.path import pwd import shutil import sys from collections import OrderedDict, defaultdict from contextlib import suppress from pathlib import Path from typing import Dict, List, Set from urllib.parse import urlparse from packaging.version import Version from defence360agent.application.determine_hosting_panel import ( is_cpanel_installed, ) from defence360agent.contracts import config from defence360agent.utils import ( CheckRunError, antivirus_mode, async_lru_cache, check_run, run, ) from defence360agent.utils.ipecho import IPEchoAPI from defence360agent.utils.kwconfig import KWConfig from .. import base from ..base import DomainData, forbid_dns_only from . import packages from .whm import WHMAPIException, whmapi1 CPANEL_PACKAGE_EXTENSIONS_PATH = Path("/var/cpanel/packages/extensions") CPANEL_HOOKS_PATH = Path("/usr/local/cpanel") PREINSTALL_PACKAGE_EXTENSIONS_PATH = ( Path(config.Packaging.DATADIR) / "cpanel/packages/extensions" ) CPANEL_USERPLANS_PATH = "/etc/userplans" CPANEL_USERDATADOMAINS_PATH = ( "/etc/userdatadomains;/var/cpanel/userdata/{user}/cache" ) AV_PLUGIN_NAME = "imunify-antivirus" IM360_PLUGIN_NAME = "imunify360" PLUGIN_NAME = AV_PLUGIN_NAME if antivirus_mode.enabled else IM360_PLUGIN_NAME PLUGIN_INSTALL_SCRIPT = "/usr/local/cpanel/scripts/install_plugin" PLUGIN_UNINSTALL_SCRIPT = "/usr/local/cpanel/scripts/uninstall_plugin" CONFIG_PATH = "/etc/sysconfig/imunify360/cpanel/" logger = logging.getLogger(__name__) CONFIG_FILE_TEMPLATE = "/etc/sysconfig/imunify360/cpanel/{name}.conf" TCP_PORTS_CPANEL = base.TCP_PORTS_COMMON + ["2086-2087"] BASE_DIR = "/home" WWWACT_CONF = "/etc/wwwacct.conf" _CACHE = {"userplans": {}, "userdatadomains": {}} class cPanelException(base.PanelException): pass class AccountConfig(KWConfig): SEARCH_PATTERN = r"^{}\s+(.*)?$" WRITE_PATTERN = "{} {}" DEFAULT_FILENAME = WWWACT_CONF class cPanel(base.AbstractPanel): NAME = "cPanel" OPEN_PORTS = { "tcp": { "in": ["143", "465", "2077-2080", "2082-2083", "2095", "2096"] + TCP_PORTS_CPANEL, "out": [ "37", "43", "113", "873", "2073", "2089", "2195", "2703", "6277", "24441", ] + TCP_PORTS_CPANEL, }, "udp": { "in": ["20", "21", "53", "443"], "out": ["20", "21", "53", "113", "123", "873", "6277", "24441"], }, } exception = cPanelException smtp_allow_users = ["cpanel"] # type: List[str] USER_INFO_DIR = "/var/cpanel/users.cache/" RESELLERS_INFO = "/var/cpanel/resellers" @staticmethod def _is_dns_only(): return os.path.isfile("/var/cpanel/dnsonly") @classmethod def get_server_ip(cls): ip_conf = "/var/cpanel/mainip" # fallback: in case there is not ip file if not os.path.exists(ip_conf): return IPEchoAPI.get_ip() with open(ip_conf) as f: return f.read().strip() @classmethod def is_installed(cls): return is_cpanel_installed() @classmethod async def version(cls): _, data, _ = await run(["/usr/local/cpanel/cpanel", "-V"]) version = data.decode().split() return version[0] if version else "unknown" @base.ensure_valid_panel() async def enable_imunify_plugin(self, name=None): plugin_name = name or PLUGIN_NAME # Allowlist (RPC-supplied); raise — assert is stripped under -O. if plugin_name not in (AV_PLUGIN_NAME, IM360_PLUGIN_NAME): raise cPanelException( "Refusing to enable plugin: invalid plugin_name %r" % (plugin_name,) ) config_filename = CONFIG_FILE_TEMPLATE.format(name=plugin_name) new_conf = config_filename + ".rpmnew" if os.path.exists(new_conf): shutil.move(new_conf, config_filename) if Version(await self.version()) > Version("65.0"): await run( [ "/bin/sed", "-i", "-e", "s@^target=.*@target=_self@g", config_filename, ] ) # (re-) register plugin sys.stdout.write("cPanel: register_appconfig...\n") await run( [ "/usr/local/cpanel/bin/register_appconfig", config_filename, ] ) @base.ensure_valid_panel() async def disable_imunify_plugin(self, plugin_name=None): plugin = plugin_name or PLUGIN_NAME # Allowlist (RPC-supplied); raise — assert is stripped under -O. if plugin not in (AV_PLUGIN_NAME, IM360_PLUGIN_NAME): raise cPanelException( "Refusing to disable plugin: invalid plugin_name %r" % (plugin,) ) config_filename = CONFIG_FILE_TEMPLATE.format(name=plugin) config_created = False if not os.path.exists(config_filename): logger.info( "Warning: cpanel " f"{plugin}.conf missing, " "creating temporary config for uninstall" ) os.makedirs(CONFIG_PATH, exist_ok=True) with open(config_filename, "w") as f: f.write("# Temporary config for uninstall\n") config_created = True sys.stderr.write("cPanel: unregister_appconfig...\n") await run( [ "/usr/local/cpanel/bin/unregister_appconfig", config_filename, ] ) if config_created: try: os.remove(config_filename) except Exception as e: logger.error(f"Failed to remove temporary config: {e}") @forbid_dns_only async def get_user_domains(self): """ :return: list: domains hosted on server via cpanel """ return [ domain for user in await self.get_users() for domain, user_path in self._userdomains(user) ] @classmethod async def get_user_domains_details( cls, username, _path=CPANEL_USERDATADOMAINS_PATH, quiet=True ) -> list[DomainData]: domains = [] def parser(path, d, domain_data): user_ = domain_data[0] if user_ != username: return doc_type = domain_data[2] docroot = domain_data[4] domains.append( DomainData( docroot=docroot, domain=d, type=doc_type, username=username ) ) cls._parse_userdatadomains(_path, parser, quiet=quiet) return domains async def _do_get_users(self, userplans_path: str) -> List[str]: if not os.path.isfile(userplans_path): return [] _cached_mtime = _CACHE["userplans"].get("mtime", 0) if _cached_mtime == os.path.getmtime(userplans_path): return _CACHE["userplans"]["users"] with open( userplans_path, encoding="utf-8", errors="surrogateescape" ) as f: users = [] for line in f: if ( not line.startswith("#") and line.count(":") == 1 and len(line.strip()) > 3 ): users.append(line.split(":")[0].strip()) _CACHE["userplans"]["mtime"] = os.path.getmtime(userplans_path) _CACHE["userplans"]["users"] = users return users async def get_users( self, ) -> List[str]: return await self._do_get_users(CPANEL_USERPLANS_PATH) async def get_domain_to_owner(self) -> Dict[str, List[str]]: """ Returns dict with domain to list of users pairs :return: dict domain to list of users: """ domain_to_users = defaultdict(list) # type: Dict[str, List[str]] for user in await self.get_users(): for domain, _ in self._userdomains(user): domain_to_users[domain].append(user) return domain_to_users async def get_domains_per_user(self): """ Returns dict with users to list of domains pairs :return: dict user to list of domains """ user_to_domains = defaultdict(list) for user in await self.get_users(): for domain, _ in self._userdomains(user): user_to_domains[user].append(domain) return user_to_domains @async_lru_cache(maxsize=128) async def panel_user_link(self, username) -> str: """ Returns panel url :return: str """ link = ( await whmapi1( "create_user_session", user=username, service="cpaneld" ) )["url"] if len(link) == 0: return "" parsed = urlparse(link) return f"{parsed.scheme}://{parsed.netloc}/cpsess0000000000/frontend/jupiter/imunify/imunify.live.pl" async def switch_ui_config(self, myimunify_enabled: bool) -> None: """ Switch UI panel configuration between Im360 and MyImunify """ if antivirus_mode.enabled: return None if not Path("/var/imunify360/i360-userside-plugin.installed").exists(): return None config_to_enable = "myimunify_conf" if myimunify_enabled else "conf" config_to_disable = "conf" if myimunify_enabled else "myimunify_conf" for theme_path in glob.glob("/usr/local/cpanel/base/frontend/*"): if Path(theme_path).is_dir(): theme_name = os.path.basename(theme_path) await self.disable_config(config_to_disable, theme_name) await self.enable_config(config_to_enable, theme_name) async def enable_config(self, config: str, theme: str) -> None: try: await check_run( [ PLUGIN_INSTALL_SCRIPT, f"{CONFIG_PATH}{config}", "--theme", theme, ] ) except CheckRunError as e: logger.warning("Error in enabling config '%s': %s", config, e) async def disable_config(self, config: str, theme: str) -> None: try: await check_run( [ PLUGIN_UNINSTALL_SCRIPT, f"{CONFIG_PATH}{config}", "--theme", theme, ] ) except CheckRunError as e: logger.warning("Error in disabling config '%s': %s", config, e) async def get_user_details(self) -> Dict[str, Dict[str, str]]: """ Returns dict with user to email and locale pairs """ user_details = {} # noinspection PyBroadException try: resellers = { line.split(":", 1)[0] for line in Path(self.RESELLERS_INFO).read_text().splitlines() } except Exception: resellers = None for user in await self.get_users(): level = base.UserLevel.REGULAR_USER if user == "root": try: with open("/etc/wwwacct.conf.cache") as f: user_info = json.load(f) email = user_info.get("CONTACTEMAIL", "") except (FileNotFoundError, json.JSONDecodeError): email = "" locale = "en" parent = "root" suspended = False level = base.UserLevel.ADMIN else: try: with open(os.path.join(self.USER_INFO_DIR, user)) as f: user_info = json.load(f) email = user_info.get("CONTACTEMAIL", "") locale = user_info.get("LOCALE", "") parent = user_info.get("OWNER", "") suspended = user_info.get("SUSPENDED", "") == "1" except (FileNotFoundError, json.JSONDecodeError): email = "" locale = "" parent = "" suspended = False if resellers and user in resellers: # 1 for the root (see above) # 2 for a reseller # 3 for a regular customer level = base.UserLevel.RESSELER user_details[user] = { "email": email, "locale": locale, "parent": parent, "suspended": suspended, "level": int(level), } return user_details @classmethod def _get_max_mtime(cls, _path): """checks mtime of userdatadomains files (including cache) returns max mtime of all files""" _mtimes = [] if "{user}" in _path: call_as_user = pwd.getpwuid(os.getuid()).pw_name _path = _path.replace("{user}", call_as_user) path_list = _path.split(";") for path_ in path_list: if os.path.exists(path_): _mtimes.append(os.path.getmtime(path_)) return max(_mtimes) if _mtimes else 0 @classmethod def _get_from_cache(cls, cpuser, _path): """check and invalidate cache if needed""" _cached_mtime = ( _CACHE["userdatadomains"].get(cpuser, {}).get("mtime", 0) ) if _cached_mtime < cls._get_max_mtime(_path): _CACHE["userdatadomains"][cpuser] = {} return None return _CACHE["userdatadomains"].get(cpuser, {}).get("domains", []) @classmethod def _userdomains( cls, cpuser, _path=CPANEL_USERDATADOMAINS_PATH, quiet=True ): cached_data = cls._get_from_cache(cpuser, _path) if cached_data is not None: return cached_data # use dict to avoid duplicates domains_tmp = OrderedDict() domains = OrderedDict() def parser(path, d, domain_data): user_ = domain_data[0] if user_ == cpuser: document_root = domain_data[4] if "main" == domain_data[2]: # main domain must be first in list domains.update({d: document_root}) else: domains_tmp.update({d: document_root}) cls._parse_userdatadomains(_path, parser, quiet=quiet) domains.update(domains_tmp) _CACHE["userdatadomains"][cpuser] = { "mtime": cls._get_max_mtime(_path), "domains": domains.items(), } return domains.items() @staticmethod def _parse_userdatadomains(_path, parser, quiet=True): if "{user}" in _path: call_as_user = pwd.getpwuid(os.getuid()).pw_name _path = _path.replace("{user}", call_as_user) path_list = _path.split(";") for path_ in path_list: try: file_ = open(path_, "rb") except Exception as e: if not quiet: logger.warning("Can't open file %s [%s]", path_, e) continue try: # example line: # test.russianguns.ru: russianguns==root==sub==russianguns.ru== # /home/russianguns/fla==192.168.122.40:80======0 for i, line in enumerate(file_): try: line = line.decode() except UnicodeDecodeError: logger.warning( 'Broken %s line in file "%s"; line was ignored', i, path_, ) continue if not line.strip(): # ignore the empty string continue if line.count(": ") != 1: if not quiet: logger.warning( "Can't parse %s line in file '%s'; " "line was ignored", i, path_, ) continue domain, domain_raw_data = line.split(": ") domain_data = domain_raw_data.strip().split("==") parser(path_, domain, domain_data) finally: file_.close() @classmethod def is_extension_installed(cls, pkgs): return all( CPANEL_PACKAGE_EXTENSIONS_PATH.joinpath(file).is_file() for file in pkgs ) @classmethod async def is_hook_installed(cls): try: hooks = await whmapi1("list_hooks") category = next( cat for cat in hooks["categories"] if cat["category"] == "Whostmgr" ) for event_name in ( "Accounts::change_package", "Accounts::Create", "Accounts::Modify", ): event = next( ev for ev in category["events"] if ev["event"] == event_name ) stage = next( st for st in event["stages"] if st["stage"] == "post" ) if not any( action["hook"] == "ImunifyHook::hook_processing" for action in stage["actions"] ): return False except (StopIteration, WHMAPIException): return False return True @classmethod async def install_extension( cls, extention_name: str, extention_files, **kwargs, ) -> None: # copy cpanel's package extension files CPANEL_PACKAGE_EXTENSIONS_PATH.mkdir( mode=0o700, parents=True, exist_ok=True ) for filename in extention_files: shutil.copy2( PREINSTALL_PACKAGE_EXTENSIONS_PATH / filename, CPANEL_PACKAGE_EXTENSIONS_PATH, ) # enable extension for all packages await packages.add_extension_for_all(extention_name, **kwargs) # add hooks for native feature management os.makedirs(config.Core.INBOX_HOOKS_DIR, mode=0o700, exist_ok=True) shutil.copy2( PREINSTALL_PACKAGE_EXTENSIONS_PATH / "ImunifyHook.pm", CPANEL_HOOKS_PATH, ) await check_run( [ "/usr/local/cpanel/bin/manage_hooks", "add", "module", "ImunifyHook", ] ) @classmethod async def uninstall_extension(cls, extension_name: str, extention_files): # remove the hook await check_run( [ "/usr/local/cpanel/bin/manage_hooks", "del", "module", "ImunifyHook", ] ) with suppress(FileNotFoundError): (CPANEL_HOOKS_PATH / "ImunifyHook.pm").unlink() # remove the package extension from all packages await packages.remove_extension_from_all(extension_name) # remove cpanel's package extension files for filename in extention_files: with suppress(FileNotFoundError): (CPANEL_PACKAGE_EXTENSIONS_PATH / filename).unlink() @staticmethod def mounts(): mounts = [] with open("/proc/mounts", "r") as f: for line in f: values = line.strip().split() if len(values) > 1: mounts.append(values[1]) return mounts def basedirs(self) -> Set[str]: """Fetch list of basedirs. On cPanel, basedir is configured as HOMEDIR variable in /etc/wwwacct.conf. Also, there is a way to specify additional mount points as containing user folders, through HOMEMATCH variable. If value from HOMEMATCH variable is contained within a mount point path, cPanel uses this directory too.""" homedir = AccountConfig("HOMEDIR").get() homematch = AccountConfig("HOMEMATCH").get() homedir = BASE_DIR if homedir is None else homedir basedirs = {BASE_DIR, homedir} if homematch is None: return basedirs for mount in self.mounts(): # exclude virtfs from basedirs (DEF-14266) if homematch in mount and not mount.startswith("/home/virtfs/"): basedirs.add(mount) return basedirs @classmethod async def notify(cls, *, message_type, params, user=None): """ Notify a customer using cPanel iContact Notifications """ if not config.AdminContacts.ENABLE_ICONTACT_NOTIFICATIONS: return False if not config.should_send_user_notifications(username=user): return False data = {"message_type": message_type, "params": params, "user": user} logger.info(f"{cls.__name__}.notify(%s)", data) cmd = ( "/usr/local/cpanel/whostmgr/docroot/cgi" "/imunify/handlers/notify.cgi" ) stdin = json.dumps(data) out = await check_run([cmd], input=stdin.encode()) return json.loads(out.decode(errors="surrogateescape")) async def list_docroots(self) -> Dict[str, str]: result = dict() def parser(path, d, domain_data): result[domain_data[4]] = domain_data[3] self._parse_userdatadomains( CPANEL_USERDATADOMAINS_PATH, parser, quiet=True ) return result async def get_domain_paths(self) -> Dict[str, List[str]]: result = {} def parser(_, d, domain_data): result[d] = [domain_data[4]] self._parse_userdatadomains( CPANEL_USERDATADOMAINS_PATH, parser, quiet=True ) return result defence360agent/subsys/panels/cpanel/whm.py0000644000000000000000000001010700000000000015766 0ustar import json import logging import subprocess import signal from urllib.parse import quote from defence360agent.utils import check_run, CheckRunError from defence360agent.subsys.panels.base import PanelException logger = logging.getLogger(__name__) # use complete path as recommended by cPanel docs # https://documentation.cpanel.net/display/DD/WHM+API+1+Functions+-+modsec_is_installed WHMAPI1_CMD = "/usr/sbin/whmapi1" WHMAPI_CERT_ERROR_LIST = [ "no certificate", "no key with the id", "cannot read license file", "invalid license file", "license file expired", ] class WHMAPIException(PanelException): """Got broken output or other problem during WHMAPI call""" pass class WHMAPILicenseError(WHMAPIException): """Raises when cannot Read License File""" pass async def whmapi1(function, sudo=False, **kwargs): cmd = ["sudo"] if sudo else [] cmd.extend([WHMAPI1_CMD, "--output=json", function]) params = ["{}={}".format(k, quote(v)) for k, v in kwargs.items()] try: raw_output = (await check_run(cmd + params)).decode() except CheckRunError as e: if e.returncode == -signal.SIGTERM: logger.warning(e) raise WHMAPIException(e) else: raise e try: output = json.loads(raw_output) except json.JSONDecodeError as e: raise WHMAPIException( f"Broken output from whmapi1: {raw_output!r}, reason: {e}" ) from e try: if output["metadata"]["result"]: return output["data"] else: raise WHMAPIException( "whmapi {} command failed: {}".format( output["metadata"]["command"], output["metadata"]["reason"] ) ) except KeyError as e: if ("statusmsg", "Cannot Read License File") in output.items(): logger.warning("Cannot Read CPanel License File") raise WHMAPILicenseError else: raise WHMAPIException( "Broken output from whmapi1 (KeyError: {}): {!r}".format( e, output ) ) def run_whmapi(args, *path_list): # FIXME: this script partly copypaste 'whmapi1' function cmd = [WHMAPI1_CMD, *args, "--output=json"] try: logger.debug("subprocess.run(%r)", cmd) res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE) except subprocess.CalledProcessError as e: raise WHMAPIException("Failed to run whmapi1: %s" % e) from e if path_list: decoded_output = json.loads(res.stdout.decode()) result = [] for i, element_path in enumerate(path_list): try: item = decoded_output for key in element_path: item = item[key] result.append(item) except KeyError as e: if i == 0: # we guarantee to *always* return first element from # path_list raise WHMAPIException( "Could not parse whmapi1 output" ) from e else: # and have no guarantee for the rest of path_list result.append(None) else: return if len(result) == 1: return result[0] else: return result def run_whmapi_result(args): return run_whmapi(args, ["metadata", "result"]) def run_whmapi_result_and_reason(args): result, reason = run_whmapi( args, ["metadata", "result"], ["metadata", "reason"] ) # explicit is better than implicit! return result, reason def catch_exception(func): async def wrapper(*args, **kwargs): rv = None try: rv = await func(*args, **kwargs) except WHMAPIException as sww: # Do not mess the output with stacktrace, # more details can be found in sentry. logger.error(str(sww)) except: # noqa # do not left unreported logger.exception("Something went wrong") return rv return wrapper defence360agent/subsys/panels/directadmin/0000755000000000000000000000000000000000000015643 5ustar defence360agent/subsys/panels/directadmin/__init__.py0000644000000000000000000000007200000000000017753 0ustar from .panel import DirectAdmin __all__ = ["DirectAdmin"] defence360agent/subsys/panels/directadmin/__pycache__/0000755000000000000000000000000000000000000020053 5ustar defence360agent/subsys/panels/directadmin/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000044400000000000025255 0ustar r_j:ddlmZdgZdS)) DirectAdminrN)panelr__all__g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/__init__.pyr s" /rdefence360agent/subsys/panels/directadmin/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000044400000000000024316 0ustar r_j:ddlmZdgZdS)) DirectAdminrN)panelr__all__g/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/__init__.pyr s" /rdefence360agent/subsys/panels/directadmin/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000000145700000000000024770 0ustar r_j:ddlZddlmZdZGddeZdS)N)KWConfigz/usr/local/directadmin/confcLeZdZdZdZejedZ dS) ConfigOptionsz^\s*{}\s*=\s*(.*?)\s*$z{}={}zdirectadmin.confN) __name__ __module__ __qualname__SEARCH_PATTERN WRITE_PATTERNospathjoinBASEDIRDEFAULT_FILENAMEe/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/config.pyrrs1.NMw||G-?@@rr)r defence360agent.utils.kwconfigrrrrrrrsc 333333 'AAAAAHAAAAArdefence360agent/subsys/panels/directadmin/__pycache__/config.cpython-311.pyc0000644000000000000000000000145700000000000024031 0ustar r_j:ddlZddlmZdZGddeZdS)N)KWConfigz/usr/local/directadmin/confcLeZdZdZdZejedZ dS) ConfigOptionsz^\s*{}\s*=\s*(.*?)\s*$z{}={}zdirectadmin.confN) __name__ __module__ __qualname__SEARCH_PATTERN WRITE_PATTERNospathjoinBASEDIRDEFAULT_FILENAMEe/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/config.pyrrs1.NMw||G-?@@rr)r defence360agent.utils.kwconfigrrrrrrrsc 333333 'AAAAAHAAAAArdefence360agent/subsys/panels/directadmin/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000007331400000000000024623 0ustar r_j&GRddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl mZddlmZddlmZmZmZmZddlmZddlmZmZddlmZddlmZmZm Z m!Z!m"Z"m#Z#dd l$m%Z%m&Z&d d l'm(Z(d d l(m)Z)ej*e+Z,d Z-dZ.dZ/dZ0d1e0e.Z2dZ3dZ4e(j5ddgzZ6dZ7e j8dZ9Gdde(j)Z:Gdde%Z;defdZe4fdee=e=ffdZ?defd Z@Gd!d"e(jAZBdS)#N) defaultdict)Path)AnyDictListSet)Version)DA_FILEis_directadmin_installed)Core)HTTP_REQUEST_RETRY_TIMEOUTasync_lru_cache backoff_sleepretry_onruntimeit) IpChooser UrlTransport)base)PanelExceptionz/homez#/usr/bin/imunify360-command-wrapperz%/usr/local/directadmin/scripts/customzimunify360-sudousersz%{0} ALL=NOPASSWD: {1}z9Defaults!/usr/bin/imunify360-command-wrapper !requirettyz/etc/virtual/domainowners2222 35000-35999z"/usr/local/directadmin/data/users/z^[a-z_][a-z0-9_-]{0,31}\ZceZdZdS)DirectAdminExceptionN)__name__ __module__ __qualname__d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/panel.pyrr4sDr rc"eZdZdededefdZdS)_LoopbackIpChooserhostnameportreturncdS)Nz 127.0.0.1r)selfr$r%s r!choosez_LoopbackIpChooser.choose9s{r N)rrrstrintr)rr r!r#r#8s=s##r r#r&cTt|tstd||S)Nz$Unexpected document roots response: ) isinstancedictr)payloads r!_valid_docroots_payloadr0=s8 gt $ $  >7 > >    Nr ct|trt|st d||S)zGReturn ``user`` if safe; otherwise raise :class:`DirectAdminException`.z.Refusing to manage sudouser: invalid username )r-r*_SUDOUSER_NAME_REmatchr)users r!_validate_sudouserr5EsN dC  (9(?(?(E(E ""BF$ H    Kr c i}t|d5}|D]} |}n4#t$r'}td|||Yd}~Dd}~wwxYw|d}|dkr<||dzd||d|< dddn #1swxYwY|S)zBReturn a mapping from domain name to user name owning this domain.rbzBroken line in %s: %r (%s)N:)opendecodeUnicodeDecodeErrorloggerwarningfindstrip)pathdomainsfblinelineeposs r!get_user_domainsrINs?G dD   FQ F FE ||~~%   ;T5!LLL ))C..Cbyy.23799o.C.C.E.ETcT ((**+ F F F F F F F F F F F F F F F F Ns7C.C AACAACC C c JKddg}t|d{V\}}} d}tj||tj}t |dS#ttf$rtd|d|d|wxYw) N"/usr/local/directadmin/directadminvs&^(Version: )?DirectAdmin (v.)?([\d.]+))flagsz-Failed to parse directadmin version. retcode=z , stdout= , stderr=) rresearch MULTILINEr groupr< ValueErrorAttributeErrorr)cmdretcodestdoutstderrversion_patternresults r!get_directadmin_versionr\^s / 5C$'HHnnnnnnGVV D?F",GGGv||A--//000  '    1 1 1" 1 1'- 1 1    s AA88*B"ceZdZdZeZdgezdgezdgdgdddZeZ e dZ e dZ e jd Ze jd Zed Zed Zd ZdZdZdZe jd-dZe jd-dZdeefdZdZdZdZde efdZ!de"fdZ#de"fdZ$de"fdZ%ede"fdZ&de"eeffdZ'de"ee"eefffdZ(de"eeffd Z)e*e+e,e-!d"e.j/j0de1fd#Z2ed"e.j/j0de1fd$Z3d"e.j/j0de1fd%Z4defd&Z5e6d'd()de"eee j7ffd*Z8d+ede9e j7fd,Z:dS). DirectAdmin465113)inout)202153443r80)rcrdrer`123r)tcpudpctSN)r clss r! is_installedzDirectAdmin.is_installed{s')))r cHKttd{VSrl)r*r\rms r!versionzDirectAdmin.versions/022222222333r c,Kt|||vs#tjddkrGt dd|t gd{V\}}}|dkr!td|||dSdSdS)Nusertypeadmingpasswdz-arz&gpasswd -a failed for %r: rc=%s err=%r) r5 _get_adminsosenvirongetr SUDO_GROUPr>r?r(r4rW_outerrs r! add_sudouserzDirectAdmin.add_sudousers4   4##%% % % )C)Cw)N)N'*ItT:+N'O'O!O!O!O!O!O!O GT3!||<  *O)N|r cKt|||vrGtdd|tgd{V\}}}|dkr!td|||dSdSdS)Nruz-drz&gpasswd -d failed for %r: rc=%s err=%r)r5rvrrzr>r?r{s r!delete_sudouserzDirectAdmin.delete_sudousers4   4##%% % %'*ItT:+N'O'O!O!O!O!O!O!O GT3!||<  & %|r ct|d5}|dz }||vr||ddddS#1swxYwYdS)Nr+ )r; readlineswrite)rBcontentrDs r! _add_linezDirectAdmin._add_lines $   ! tOGakkmm++    ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !s1AAAct|d5}dfd|D}|d|d||ddddS#1swxYwYdS)Nrc3HK|]}|v|VdSrl)rA).0rFrs r! z+DirectAdmin._remove_line..s5MMD 1L1L41L1L1L1LMMr r)r;joinseektruncater)rBrrDdatas ` r! _remove_linezDirectAdmin._remove_lines $   77MMMMAMMMMMD FF1III JJqMMM GGDMMM                   sA!BBBctdd5}|}dddn #1swxYwY|S)Nz,/usr/local/directadmin/data/admin/admin.listrr;readsplit)r(rD admin_lists r!rvzDirectAdmin._get_adminss @# F F *!))J * * * * * * * * * * * * * * *'AA Actdd5}|}dddn #1swxYwY|S)Nz//usr/local/directadmin/data/admin/reseller.listrr)r(rD reseller_lists r!_get_resellerszDirectAdmin._get_resellerss CS I I -QFFHHNN,,M - - - - - - - - - - - - - - -rctjt|}tj|st |d||dtj dj }tj dj }tj |||tj |d|||dS)Nwz #!/bin/shdiradmini) rwrBr HOOKS_DIRexistsr;closerpwdgetpwnampw_uidchownchmod)r(hookrrBuidgids r! _create_hookzDirectAdmin._create_hooksw||It,,w~~d## " sOO ! ! # # # NN4 - - -,z**1C,z**1C HT3 $ $ $ HT5 ! ! ! tW%%%%%r ctjt|}tj|r|||dSdSrl)rwrBrrrr)r(rrrBs r! _delete_hookzDirectAdmin._delete_hooksSw||It,, 7>>$   -   dG , , , , , - -r NcKtjdt|dt |dt |D]Q} ||d{V#t$r&}t d||Yd}~Jd}~wwxYw| dd| dd| dddS) Nz/usr/sbin/groupadd -f {} /etc/sudoers&Skipping invalid sudouser entry %r: %suser_create_post.sh9/usr/bin/imunify360-agent add-sudouser --user "$username"user_destroy_pre.shr?r)r(namer4excs r!enable_imunify_pluginz!DirectAdmin.enable_imunify_pluginsU ,33J??@@@ ~y111 ~}555$$&&  D ''----------'   r?rwrrrzr)r( plugin_namer4rs r!disable_imunify_pluginz"DirectAdmin.disable_imunify_pluginsY .)444 .-888$$&&  D **40000000000'   z3DirectAdmin.get_domain_to_owner..s NNN<64NNNr )rIitemsrs r!get_domain_to_ownerzDirectAdmin.get_domain_to_owner s0ON3C3E3E3K3K3M3MNNNNr cKtt}tD] \}}|||!|S)z8 :return: user to list of domains pairs )rrrIrappend)r(user_to_domainsrr4s r!get_domains_per_userz DirectAdmin.get_domains_per_users\&d++,..4466 1 1LFD D ! ( ( 0 0 0 0r cthSrl)BASE_DIRrs r!basedirszDirectAdmin.basedirss zr cKtd{Vtdkr|d{VS|d{VS)Nz1.62.8)r\r docroots_info_newdocroots_info_legacyrs r! docroots_infozDirectAdmin.docroots_infosy(** * * * * * *gh.?.? ? ?//11111111 1..000000000r cKddg}tdt5t|d{V\}}}dddn #1swxYwY|dkrtd|d|d|tj|}|j d\}}tj | }d || d g}td |t%j} tj|d d|id} t-| d|j| d{VS#t2$r } td} | | _Yd} ~ nd} ~ wt$r } | } Yd} ~ nd} ~ wwxYw|jdkr| td|  t-| d|j| d{VS#t$r| wxYw)NrKz--root-auth-urlz!Call DA binary to obtain auth URLrz2Failed to obtain auth URL. Unexpected return code . stdout=rO@/)netlocz0CMD_API_DOMAIN?json=yes&action=document_root_allzDocument roots URL: %s AuthorizationzBasic GET)headersmethodz5Timed out obtaining document roots from the panel APIhttpszDPanel API document roots request failed (%s), retrying over loopback)rr>rrurllibparseurlparser<rArrbase64standard_b64encodeencoder_replacegeturlinfoasyncioget_event_looprequestRequestr0run_in_executor _do_request TimeoutError __cause__schemer?_do_loopback_request) r(rVrWrXrY parsed_url basic_authrdocument_roots_urllooprrG primary_errors r!rzDirectAdmin.docroots_info_new#s=35FG 7 @ @ 5 5,/HHnnnnnn #GVV 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 a<< 9W99!99.499  \**6==??+@+@+B+BCC '.44S99 F.z/@/@/B/BCCJJLL  XX##6#2299;;B     ,.@AAA%''.(( $&;z&;&;<)   ***41A7KKKKKKKK  ( ( (-;G--M'(M # # # # # #   MMMMMM    ' '  &     ***$3W       sAAA A.G HG** H7G>>H/.I I+cKddg}tdt5t|d{V\}}}dddn #1swxYwY|dkr|dkrtd|d|d| t j|}n*#tj$r}td |d d}~wwxYwt|S) NrKz--DocumentRootz%Call DA binary to obtain all docrootsrr:z8Failed to obtain document roots. Unexpected return code rrOz7Failed to obtain document roots. Failed to decode json .) rr>rrjsonloadsr<JSONDecodeErrorr0)r(rVretrbr}outputrGs r!rz DirectAdmin.docroots_info_legacybsW 0  ;V D D + +"%c((NNNNNNMCc + + + + + + + + + + + + + + + !88q :::"%::25::  Z --FF#    N!NNN   'v...s)AA A2&BC(B;;Cc~t}|dD]\}}|dD]s\}}|dr |||d<|diD]%\}}|dr |||d<&t|S)NusersrC public_html subdomains)r.rry)rrusernameuserdata domainname domaindata_sub_datas r!parse_document_root_outputz&DirectAdmin.parse_document_root_outputwsff"(/"7"7"9"9 B B Hh*29*=*C*C*E*E B B& J>>-00@5?C =12#->>,#C#C#I#I#K#KBBKAx||M22B7AH]34B B  r cdK|d{V}||Srl)rr )r(rs r! list_docrootszDirectAdmin.list_docrootss=''))))))))..t444r cKi}|d{V}t|}t|}|D]} ||}t jj}||vrt jj}||vrt jj }| dd| dd| dd| ddkt|d||<#t$r.}ddd||<td ||Yd}~d}~wwxYw|S) Nlanguageremailcreator suspendedyes)localerparentrlevel)rrz!Failed to get_user_details: %s %s)rrrvrget_user_details_for_usernamer UserLevel REGULAR_USERRESSELERADMINryr+ Exceptionr>r?) r(res usernamesadmins resellersr parsed_configrrGs r!get_user_detailszDirectAdmin.get_user_detailss..******** T%%''((++--.. !  H  $ B B8 L L 3y(( N3Ev%% N0E+// B??*..w;;+// 2>>!.!2!2;!?!?5!H ZZ !!H     !!H 71   s&B;D"" E,$EEctt|d}tj}d|z}|||d}|S)z Implementation taken from https://github.com/patchman-cloudlinux/patchman2-client/blob/05f54db63639b939c055a9543e82bc9690559965/src/platform/platforms/directadmin.cpp#L315 directadmin::get_user_details z /user.confz[top] top)rUSERS_CONF_DIR read_text configparser ConfigParser read_string)r(r user_conf_strr!s r!rz)DirectAdmin.get_user_details_for_usernamesk  x333  )++ %133 !M1 !!-000%e, r )on_errortimeoutrcL||tjjSrl) _fetch_jsonrrurlopen)r(rs r!rzDirectAdmin._do_requests )?@@@r c ||tj5}|jdkr'td|jt j|cdddS#1swxYwYdS#ttj j t j tjt jjf$r }t|d}~wwxYw)N)r,zstatus code is {})r DEFAULT_SOCKET_TIMEOUTstatusrrrrrr<r=httpclient HTTPExceptionrsocketr,rerrorURLError)r open_funcresponserGs r!r.zDirectAdmin._fetch_jsons( (!< <?c))(+228?CCz(--//"8"8":":;; < < < < < < < < < < < < < < < < < <  K %  N L !   ( ( (!a ' (s<BA*B BBBBBAC+C&&C+crttd}|||jS)NF) ip_chooser use_proxies)rr#r.r;)r(r transports r!rz DirectAdmin._do_loopback_requests; )++   888r c KdS)z8 Returns panel url :return: str rr)r(rs r!panel_user_linkzDirectAdmin.panel_user_links rr r:<)maxsizettlc K|d{V}tt}|diD]\}}|diD]\}}|d}|r1||t j||d||dpiD]R\}} | d} | r6||t j| |d|d|Sʌ|S) NrrCrmain)docrootrtyperrrsub)rrrryrrr DomainData) r(rr[r4rr domain_datarrIr sub_public_htmls r!_get_domains_details_per_userz)DirectAdmin._get_domains_details_per_users''))))))))3>t3D3D"hhw3399;;  ND('/||Ir'B'B'H'H'J'J  # )oom<< 4L''$/#)!'%)  OOL117R%''  MC'/ll=&A&AO&t ++ O(7*-'8'8'8'8%*)-   0 r rcK|d{V}t||gSrl)rMrry)r(rdetailss r!get_user_domains_detailsz$DirectAdmin.get_user_domains_detailssG::<<<<<<<<GKK"--...r rl);rrrNAMEr DA_BINARY TCP_PORTS_DA OPEN_PORTSr exception classmethodrorqrensure_valid_panelr~r staticmethodrrrvrrrrrrr*rrIrrrrrrrrr r r"rrrrr rrrrrr.rrArrJrMrrPrr r!r^r^lsV DI'L(7\)  A@@BBB   J%I**[*44[4T   T   !!\! \  & & &--- T    4T    46c6666 /// OOO #c(1T1111 = = = = = ~/D////* d   \ 5T#s(^5555S$sCx.-@(A< c3h    X* A6>#9AcAAA  A(V^3(3(((\(&9FN,B9s9999 _QB''' c4(( )('@// do //////r r^)Crrr' http.clientr4rloggingrwrrPr7r urllib.parse collectionsrpathlibrtypingrrrrpackaging.versionr 3defence360agent.application.determine_hosting_panelr r defence360agent.contracts.configr defence360agent.utilsr rrrrr#defence360agent.utils.net_transportrrrrr getLoggerrr>rCMDrrzrrr_VIRTUAL_DOMAINOWNERSTCP_PORTS_COMMONrSr%compiler2rr#r0r*r5rIr\ AbstractPanelr^rr r!rjs%   ######''''''''''''%%%%%%211111HGGGGGGG!!!!!!  8 $ $ + 3 # $ + +J < < K 3$ '>> 5BJ;<<     4.    0  DcN      w     V/V/V/V/V/$$V/V/V/V/V/r defence360agent/subsys/panels/directadmin/__pycache__/panel.cpython-311.pyc0000644000000000000000000007331400000000000023664 0ustar r_j&GRddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl mZddlmZddlmZmZmZmZddlmZddlmZmZddlmZddlmZmZm Z m!Z!m"Z"m#Z#dd l$m%Z%m&Z&d d l'm(Z(d d l(m)Z)ej*e+Z,d Z-dZ.dZ/dZ0d1e0e.Z2dZ3dZ4e(j5ddgzZ6dZ7e j8dZ9Gdde(j)Z:Gdde%Z;defdZe4fdee=e=ffdZ?defd Z@Gd!d"e(jAZBdS)#N) defaultdict)Path)AnyDictListSet)Version)DA_FILEis_directadmin_installed)Core)HTTP_REQUEST_RETRY_TIMEOUTasync_lru_cache backoff_sleepretry_onruntimeit) IpChooser UrlTransport)base)PanelExceptionz/homez#/usr/bin/imunify360-command-wrapperz%/usr/local/directadmin/scripts/customzimunify360-sudousersz%{0} ALL=NOPASSWD: {1}z9Defaults!/usr/bin/imunify360-command-wrapper !requirettyz/etc/virtual/domainowners2222 35000-35999z"/usr/local/directadmin/data/users/z^[a-z_][a-z0-9_-]{0,31}\ZceZdZdS)DirectAdminExceptionN)__name__ __module__ __qualname__d/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/directadmin/panel.pyrr4sDr rc"eZdZdededefdZdS)_LoopbackIpChooserhostnameportreturncdS)Nz 127.0.0.1r)selfr$r%s r!choosez_LoopbackIpChooser.choose9s{r N)rrrstrintr)rr r!r#r#8s=s##r r#r&cTt|tstd||S)Nz$Unexpected document roots response: ) isinstancedictr)payloads r!_valid_docroots_payloadr0=s8 gt $ $  >7 > >    Nr ct|trt|st d||S)zGReturn ``user`` if safe; otherwise raise :class:`DirectAdminException`.z.Refusing to manage sudouser: invalid username )r-r*_SUDOUSER_NAME_REmatchr)users r!_validate_sudouserr5EsN dC  (9(?(?(E(E ""BF$ H    Kr c i}t|d5}|D]} |}n4#t$r'}td|||Yd}~Dd}~wwxYw|d}|dkr<||dzd||d|< dddn #1swxYwY|S)zBReturn a mapping from domain name to user name owning this domain.rbzBroken line in %s: %r (%s)N:)opendecodeUnicodeDecodeErrorloggerwarningfindstrip)pathdomainsfblinelineeposs r!get_user_domainsrINs?G dD   FQ F FE ||~~%   ;T5!LLL ))C..Cbyy.23799o.C.C.E.ETcT ((**+ F F F F F F F F F F F F F F F F Ns7C.C AACAACC C c JKddg}t|d{V\}}} d}tj||tj}t |dS#ttf$rtd|d|d|wxYw) N"/usr/local/directadmin/directadminvs&^(Version: )?DirectAdmin (v.)?([\d.]+))flagsz-Failed to parse directadmin version. retcode=z , stdout= , stderr=) rresearch MULTILINEr groupr< ValueErrorAttributeErrorr)cmdretcodestdoutstderrversion_patternresults r!get_directadmin_versionr\^s / 5C$'HHnnnnnnGVV D?F",GGGv||A--//000  '    1 1 1" 1 1'- 1 1    s AA88*B"ceZdZdZeZdgezdgezdgdgdddZeZ e dZ e dZ e jd Ze jd Zed Zed Zd ZdZdZdZe jd-dZe jd-dZdeefdZdZdZdZde efdZ!de"fdZ#de"fdZ$de"fdZ%ede"fdZ&de"eeffdZ'de"ee"eefffdZ(de"eeffd Z)e*e+e,e-!d"e.j/j0de1fd#Z2ed"e.j/j0de1fd$Z3d"e.j/j0de1fd%Z4defd&Z5e6d'd()de"eee j7ffd*Z8d+ede9e j7fd,Z:dS). DirectAdmin465113)inout)202153443r80)rcrdrer`123r)tcpudpctSN)r clss r! is_installedzDirectAdmin.is_installed{s')))r cHKttd{VSrl)r*r\rms r!versionzDirectAdmin.versions/022222222333r c,Kt|||vs#tjddkrGt dd|t gd{V\}}}|dkr!td|||dSdSdS)Nusertypeadmingpasswdz-arz&gpasswd -a failed for %r: rc=%s err=%r) r5 _get_adminsosenvirongetr SUDO_GROUPr>r?r(r4rW_outerrs r! add_sudouserzDirectAdmin.add_sudousers4   4##%% % % )C)Cw)N)N'*ItT:+N'O'O!O!O!O!O!O!O GT3!||<  *O)N|r cKt|||vrGtdd|tgd{V\}}}|dkr!td|||dSdSdS)Nruz-drz&gpasswd -d failed for %r: rc=%s err=%r)r5rvrrzr>r?r{s r!delete_sudouserzDirectAdmin.delete_sudousers4   4##%% % %'*ItT:+N'O'O!O!O!O!O!O!O GT3!||<  & %|r ct|d5}|dz }||vr||ddddS#1swxYwYdS)Nr+ )r; readlineswrite)rBcontentrDs r! _add_linezDirectAdmin._add_lines $   ! tOGakkmm++    ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !s1AAAct|d5}dfd|D}|d|d||ddddS#1swxYwYdS)Nrc3HK|]}|v|VdSrl)rA).0rFrs r! z+DirectAdmin._remove_line..s5MMD 1L1L41L1L1L1LMMr r)r;joinseektruncater)rBrrDdatas ` r! _remove_linezDirectAdmin._remove_lines $   77MMMMAMMMMMD FF1III JJqMMM GGDMMM                   sA!BBBctdd5}|}dddn #1swxYwY|S)Nz,/usr/local/directadmin/data/admin/admin.listrr;readsplit)r(rD admin_lists r!rvzDirectAdmin._get_adminss @# F F *!))J * * * * * * * * * * * * * * *'AA Actdd5}|}dddn #1swxYwY|S)Nz//usr/local/directadmin/data/admin/reseller.listrr)r(rD reseller_lists r!_get_resellerszDirectAdmin._get_resellerss CS I I -QFFHHNN,,M - - - - - - - - - - - - - - -rctjt|}tj|st |d||dtj dj }tj dj }tj |||tj |d|||dS)Nwz #!/bin/shdiradmini) rwrBr HOOKS_DIRexistsr;closerpwdgetpwnampw_uidchownchmod)r(hookrrBuidgids r! _create_hookzDirectAdmin._create_hooksw||It,,w~~d## " sOO ! ! # # # NN4 - - -,z**1C,z**1C HT3 $ $ $ HT5 ! ! ! tW%%%%%r ctjt|}tj|r|||dSdSrl)rwrBrrrr)r(rrrBs r! _delete_hookzDirectAdmin._delete_hooksSw||It,, 7>>$   -   dG , , , , , - -r NcKtjdt|dt |dt |D]Q} ||d{V#t$r&}t d||Yd}~Jd}~wwxYw| dd| dd| dddS) Nz/usr/sbin/groupadd -f {} /etc/sudoers&Skipping invalid sudouser entry %r: %suser_create_post.sh9/usr/bin/imunify360-agent add-sudouser --user "$username"user_destroy_pre.shr?r)r(namer4excs r!enable_imunify_pluginz!DirectAdmin.enable_imunify_pluginsU ,33J??@@@ ~y111 ~}555$$&&  D ''----------'   r?rwrrrzr)r( plugin_namer4rs r!disable_imunify_pluginz"DirectAdmin.disable_imunify_pluginsY .)444 .-888$$&&  D **40000000000'   z3DirectAdmin.get_domain_to_owner..s NNN<64NNNr )rIitemsrs r!get_domain_to_ownerzDirectAdmin.get_domain_to_owner s0ON3C3E3E3K3K3M3MNNNNr cKtt}tD] \}}|||!|S)z8 :return: user to list of domains pairs )rrrIrappend)r(user_to_domainsrr4s r!get_domains_per_userz DirectAdmin.get_domains_per_users\&d++,..4466 1 1LFD D ! ( ( 0 0 0 0r cthSrl)BASE_DIRrs r!basedirszDirectAdmin.basedirss zr cKtd{Vtdkr|d{VS|d{VS)Nz1.62.8)r\r docroots_info_newdocroots_info_legacyrs r! docroots_infozDirectAdmin.docroots_infosy(** * * * * * *gh.?.? ? ?//11111111 1..000000000r cKddg}tdt5t|d{V\}}}dddn #1swxYwY|dkrtd|d|d|tj|}|j d\}}tj | }d || d g}td |t%j} tj|d d|id} t-| d|j| d{VS#t2$r } td} | | _Yd} ~ nd} ~ wt$r } | } Yd} ~ nd} ~ wwxYw|jdkr| td|  t-| d|j| d{VS#t$r| wxYw)NrKz--root-auth-urlz!Call DA binary to obtain auth URLrz2Failed to obtain auth URL. Unexpected return code . stdout=rO@/)netlocz0CMD_API_DOMAIN?json=yes&action=document_root_allzDocument roots URL: %s AuthorizationzBasic GET)headersmethodz5Timed out obtaining document roots from the panel APIhttpszDPanel API document roots request failed (%s), retrying over loopback)rr>rrurllibparseurlparser<rArrbase64standard_b64encodeencoder_replacegeturlinfoasyncioget_event_looprequestRequestr0run_in_executor _do_request TimeoutError __cause__schemer?_do_loopback_request) r(rVrWrXrY parsed_url basic_authrdocument_roots_urllooprrG primary_errors r!rzDirectAdmin.docroots_info_new#s=35FG 7 @ @ 5 5,/HHnnnnnn #GVV 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 a<< 9W99!99.499  \**6==??+@+@+B+BCC '.44S99 F.z/@/@/B/BCCJJLL  XX##6#2299;;B     ,.@AAA%''.(( $&;z&;&;<)   ***41A7KKKKKKKK  ( ( (-;G--M'(M # # # # # #   MMMMMM    ' '  &     ***$3W       sAAA A.G HG** H7G>>H/.I I+cKddg}tdt5t|d{V\}}}dddn #1swxYwY|dkr|dkrtd|d|d| t j|}n*#tj$r}td |d d}~wwxYwt|S) NrKz--DocumentRootz%Call DA binary to obtain all docrootsrr:z8Failed to obtain document roots. Unexpected return code rrOz7Failed to obtain document roots. Failed to decode json .) rr>rrjsonloadsr<JSONDecodeErrorr0)r(rVretrbr}outputrGs r!rz DirectAdmin.docroots_info_legacybsW 0  ;V D D + +"%c((NNNNNNMCc + + + + + + + + + + + + + + + !88q :::"%::25::  Z --FF#    N!NNN   'v...s)AA A2&BC(B;;Cc~t}|dD]\}}|dD]s\}}|dr |||d<|diD]%\}}|dr |||d<&t|S)NusersrC public_html subdomains)r.rry)rrusernameuserdata domainname domaindata_sub_datas r!parse_document_root_outputz&DirectAdmin.parse_document_root_outputwsff"(/"7"7"9"9 B B Hh*29*=*C*C*E*E B B& J>>-00@5?C =12#->>,#C#C#I#I#K#KBBKAx||M22B7AH]34B B  r cdK|d{V}||Srl)rr )r(rs r! list_docrootszDirectAdmin.list_docrootss=''))))))))..t444r cKi}|d{V}t|}t|}|D]} ||}t jj}||vrt jj}||vrt jj }| dd| dd| dd| ddkt|d||<#t$r.}ddd||<td ||Yd}~d}~wwxYw|S) Nlanguageremailcreator suspendedyes)localerparentrlevel)rrz!Failed to get_user_details: %s %s)rrrvrget_user_details_for_usernamer UserLevel REGULAR_USERRESSELERADMINryr+ Exceptionr>r?) r(res usernamesadmins resellersr parsed_configrrGs r!get_user_detailszDirectAdmin.get_user_detailss..******** T%%''((++--.. !  H  $ B B8 L L 3y(( N3Ev%% N0E+// B??*..w;;+// 2>>!.!2!2;!?!?5!H ZZ !!H     !!H 71   s&B;D"" E,$EEctt|d}tj}d|z}|||d}|S)z Implementation taken from https://github.com/patchman-cloudlinux/patchman2-client/blob/05f54db63639b939c055a9543e82bc9690559965/src/platform/platforms/directadmin.cpp#L315 directadmin::get_user_details z /user.confz[top] top)rUSERS_CONF_DIR read_text configparser ConfigParser read_string)r(r user_conf_strr!s r!rz)DirectAdmin.get_user_details_for_usernamesk  x333  )++ %133 !M1 !!-000%e, r )on_errortimeoutrcL||tjjSrl) _fetch_jsonrrurlopen)r(rs r!rzDirectAdmin._do_requests )?@@@r c ||tj5}|jdkr'td|jt j|cdddS#1swxYwYdS#ttj j t j tjt jjf$r }t|d}~wwxYw)N)r,zstatus code is {})r DEFAULT_SOCKET_TIMEOUTstatusrrrrrr<r=httpclient HTTPExceptionrsocketr,rerrorURLError)r open_funcresponserGs r!r.zDirectAdmin._fetch_jsons( (!< <?c))(+228?CCz(--//"8"8":":;; < < < < < < < < < < < < < < < < < <  K %  N L !   ( ( (!a ' (s<BA*B BBBBBAC+C&&C+crttd}|||jS)NF) ip_chooser use_proxies)rr#r.r;)r(r transports r!rz DirectAdmin._do_loopback_requests; )++   888r c KdS)z8 Returns panel url :return: str rr)r(rs r!panel_user_linkzDirectAdmin.panel_user_links rr r:<)maxsizettlc K|d{V}tt}|diD]\}}|diD]\}}|d}|r1||t j||d||dpiD]R\}} | d} | r6||t j| |d|d|Sʌ|S) NrrCrmain)docrootrtyperrrsub)rrrryrrr DomainData) r(rr[r4rr domain_datarrIr sub_public_htmls r!_get_domains_details_per_userz)DirectAdmin._get_domains_details_per_users''))))))))3>t3D3D"hhw3399;;  ND('/||Ir'B'B'H'H'J'J  # )oom<< 4L''$/#)!'%)  OOL117R%''  MC'/ll=&A&AO&t ++ O(7*-'8'8'8'8%*)-   0 r rcK|d{V}t||gSrl)rMrry)r(rdetailss r!get_user_domains_detailsz$DirectAdmin.get_user_domains_detailssG::<<<<<<<<GKK"--...r rl);rrrNAMEr DA_BINARY TCP_PORTS_DA OPEN_PORTSr exception classmethodrorqrensure_valid_panelr~r staticmethodrrrvrrrrrrr*rrIrrrrrrrrr r r"rrrrr rrrrrr.rrArrJrMrrPrr r!r^r^lsV DI'L(7\)  A@@BBB   J%I**[*44[4T   T   !!\! \  & & &--- T    4T    46c6666 /// OOO #c(1T1111 = = = = = ~/D////* d   \ 5T#s(^5555S$sCx.-@(A< c3h    X* A6>#9AcAAA  A(V^3(3(((\(&9FN,B9s9999 _QB''' c4(( )('@// do //////r r^)Crrr' http.clientr4rloggingrwrrPr7r urllib.parse collectionsrpathlibrtypingrrrrpackaging.versionr 3defence360agent.application.determine_hosting_panelr r defence360agent.contracts.configr defence360agent.utilsr rrrrr#defence360agent.utils.net_transportrrrrr getLoggerrr>rCMDrrzrrr_VIRTUAL_DOMAINOWNERSTCP_PORTS_COMMONrSr%compiler2rr#r0r*r5rIr\ AbstractPanelr^rr r!rjs%   ######''''''''''''%%%%%%211111HGGGGGGG!!!!!!  8 $ $ + 3 # $ + +J < < K 3$ '>> 5BJ;<<     4.    0  DcN      w     V/V/V/V/V/$$V/V/V/V/V/r defence360agent/subsys/panels/directadmin/config.py0000644000000000000000000000042500000000000017463 0ustar import os from defence360agent.utils.kwconfig import KWConfig BASEDIR = "/usr/local/directadmin/conf" class ConfigOptions(KWConfig): SEARCH_PATTERN = r"^\s*{}\s*=\s*(.*?)\s*$" WRITE_PATTERN = "{}={}" DEFAULT_FILENAME = os.path.join(BASEDIR, "directadmin.conf") defence360agent/subsys/panels/directadmin/panel.py0000644000000000000000000004344600000000000017327 0ustar import asyncio import base64 import configparser import http.client import json import logging import os import pwd import re import socket import urllib import urllib.parse from collections import defaultdict from pathlib import Path from typing import Any, Dict, List, Set from packaging.version import Version from defence360agent.application.determine_hosting_panel import ( DA_FILE, is_directadmin_installed, ) from defence360agent.contracts.config import Core from defence360agent.utils import ( HTTP_REQUEST_RETRY_TIMEOUT, async_lru_cache, backoff_sleep, retry_on, run, timeit, ) from defence360agent.utils.net_transport import IpChooser, UrlTransport from .. import base from ..base import PanelException logger = logging.getLogger(__name__) BASE_DIR = "/home" CMD = "/usr/bin/imunify360-command-wrapper" HOOKS_DIR = "/usr/local/directadmin/scripts/custom" SUDO_GROUP = "imunify360-sudousers" SUDO_LINE = "%{0} ALL=NOPASSWD: {1}".format(SUDO_GROUP, CMD) SUDO_TTY_LINE = "Defaults!/usr/bin/imunify360-command-wrapper !requiretty" _VIRTUAL_DOMAINOWNERS = "/etc/virtual/domainowners" TCP_PORTS_DA = base.TCP_PORTS_COMMON + ["2222", "35000-35999"] USERS_CONF_DIR = "/usr/local/directadmin/data/users/" _SUDOUSER_NAME_RE = re.compile(r"^[a-z_][a-z0-9_-]{0,31}\Z") class DirectAdminException(base.PanelException): pass class _LoopbackIpChooser(IpChooser): def choose(self, hostname: str, port: int) -> str: return "127.0.0.1" def _valid_docroots_payload(payload) -> Dict: if not isinstance(payload, dict): raise PanelException( f"Unexpected document roots response: {payload!r}" ) return payload def _validate_sudouser(user) -> str: """Return ``user`` if safe; otherwise raise :class:`DirectAdminException`.""" if not isinstance(user, str) or not _SUDOUSER_NAME_RE.match(user): raise DirectAdminException( "Refusing to manage sudouser: invalid username %r" % (user,) ) return user def get_user_domains(path=_VIRTUAL_DOMAINOWNERS) -> Dict[str, str]: """Return a mapping from domain name to user name owning this domain.""" domains = {} with open(path, "rb") as f: for bline in f: try: line = bline.decode() except UnicodeDecodeError as e: logger.warning("Broken line in %s: %r (%s)", path, bline, e) continue pos = line.find(":") if pos != -1: domains[line[:pos].strip()] = line[pos + 1 :].strip() return domains async def get_directadmin_version() -> Version: cmd = ["/usr/local/directadmin/directadmin", "v"] retcode, stdout, stderr = await run(cmd) try: version_pattern = rb"^(Version: )?DirectAdmin (v.)?([\d.]+)" result = re.search(version_pattern, stdout, flags=re.MULTILINE) return Version(result.group(3).decode()) except (ValueError, AttributeError): raise PanelException( "Failed to parse directadmin version." f" {retcode=}, {stdout=}, {stderr=}" ) class DirectAdmin(base.AbstractPanel): NAME = "DirectAdmin" DA_BINARY = DA_FILE OPEN_PORTS = { "tcp": { "in": ["465"] + TCP_PORTS_DA, "out": ["113"] + TCP_PORTS_DA, }, "udp": { "in": ["20", "21", "53", "443", "35000-35999", "80"], "out": ["20", "21", "53", "113", "123", "35000-35999"], }, } exception = DirectAdminException @classmethod def is_installed(cls): return is_directadmin_installed() @classmethod async def version(cls): # example output 'Version: DirectAdmin v.1.53.0' return str(await get_directadmin_version()) @base.ensure_valid_panel() async def add_sudouser(self, user): _validate_sudouser(user) if user in self._get_admins() or os.environ.get("usertype") == "admin": retcode, _out, err = await run(["gpasswd", "-a", user, SUDO_GROUP]) if retcode != 0: # Tolerate non-zero exit (matches prior os.system behaviour); # batch callers rely on this, e.g. gpasswd -d on absent users. logger.warning( "gpasswd -a failed for %r: rc=%s err=%r", user, retcode, err, ) @base.ensure_valid_panel() async def delete_sudouser(self, user): _validate_sudouser(user) if user in self._get_admins(): retcode, _out, err = await run(["gpasswd", "-d", user, SUDO_GROUP]) if retcode != 0: # See add_sudouser; tolerate non-zero exit. logger.warning( "gpasswd -d failed for %r: rc=%s err=%r", user, retcode, err, ) @staticmethod def _add_line(path, content): with open(path, "r+") as f: content += "\n" if content not in f.readlines(): f.write(content) @staticmethod def _remove_line(path, content): with open(path, "r+") as f: data = "".join(line for line in f if content not in line.strip()) f.seek(0) f.truncate(0) f.write(data) def _get_admins(self): with open("/usr/local/directadmin/data/admin/admin.list", "r") as f: admin_list = f.read().split() return admin_list def _get_resellers(self): with open("/usr/local/directadmin/data/admin/reseller.list", "r") as f: reseller_list = f.read().split() return reseller_list def _create_hook(self, hook, content): path = os.path.join(HOOKS_DIR, hook) if not os.path.exists(path): open(path, "w").close() self._add_line(path, "#!/bin/sh") uid = pwd.getpwnam("diradmin").pw_uid gid = pwd.getpwnam("diradmin").pw_uid os.chown(path, uid, gid) os.chmod(path, 0o700) self._add_line(path, content) def _delete_hook(self, hook, content): path = os.path.join(HOOKS_DIR, hook) if os.path.exists(path): self._remove_line(path, content) @base.ensure_valid_panel() async def enable_imunify_plugin(self, name=None): os.system("/usr/sbin/groupadd -f {}".format(SUDO_GROUP)) self._add_line("/etc/sudoers", SUDO_LINE) self._add_line("/etc/sudoers", SUDO_TTY_LINE) for user in self._get_admins(): try: await self.add_sudouser(user) except DirectAdminException as exc: logger.warning( "Skipping invalid sudouser entry %r: %s", user, exc ) self._create_hook( "user_create_post.sh", '/usr/bin/imunify360-agent add-sudouser --user "$username"', ) self._create_hook( "user_destroy_pre.sh", '/usr/bin/imunify360-agent delete-sudouser --user "$username"', ) self._create_hook( "user_restore_post.sh", '/usr/bin/imunify360-agent add-sudouser --user "$username"', ) @base.ensure_valid_panel() async def disable_imunify_plugin(self, plugin_name=None): self._remove_line("/etc/sudoers", SUDO_LINE) self._remove_line("/etc/sudoers", SUDO_TTY_LINE) for user in self._get_admins(): try: await self.delete_sudouser(user) except DirectAdminException as exc: logger.warning( "Skipping invalid sudouser entry %r: %s", user, exc ) os.system("/usr/sbin/groupdel {}".format(SUDO_GROUP)) self._delete_hook( "user_create_post.sh", '/usr/bin/imunify360-agent add-sudouser --user "$username"', ) self._delete_hook( "user_destroy_pre.sh", '/usr/bin/imunify360-agent delete-sudouser --user "$username"', ) self._delete_hook( "user_restore_post.sh", '/usr/bin/imunify360-agent add-sudouser --user "$username"', ) async def get_users(self) -> List[str]: """ :return: list: list of directadmin users """ return list(set(get_user_domains().values())) async def get_user_domains(self): """ :return: list: domains hosted on server via directadmin """ return list(get_user_domains().keys()) async def get_domain_to_owner(self): """ :return: domain to list of users pairs """ return {domain: [user] for domain, user in get_user_domains().items()} async def get_domains_per_user(self): """ :return: user to list of domains pairs """ user_to_domains = defaultdict(list) for domain, user in get_user_domains().items(): user_to_domains[user].append(domain) return user_to_domains def basedirs(self) -> Set[str]: return {BASE_DIR} async def docroots_info(self) -> Dict: if await get_directadmin_version() >= Version("1.62.8"): return await self.docroots_info_new() return await self.docroots_info_legacy() async def docroots_info_new(self) -> Dict: cmd = ["/usr/local/directadmin/directadmin", "--root-auth-url"] with timeit("Call DA binary to obtain auth URL", logger): retcode, stdout, stderr = await run(cmd) if retcode != 0: raise PanelException( f"Failed to obtain auth URL. Unexpected return code {retcode}." f" stdout={stdout!r}, stderr={stderr!r}" ) parsed_url = urllib.parse.urlparse(stdout.decode().strip()) basic_auth, domain = parsed_url.netloc.split("@") basic_auth = base64.standard_b64encode(basic_auth.encode()).decode() document_roots_url = "/".join( [ parsed_url._replace(netloc=domain).geturl(), "CMD_API_DOMAIN?json=yes&action=document_root_all", ] ) logger.info("Document roots URL: %s", document_roots_url) loop = asyncio.get_event_loop() request = urllib.request.Request( document_roots_url, headers={"Authorization": f"Basic {basic_auth}"}, method="GET", ) try: return _valid_docroots_payload( await loop.run_in_executor(None, self._do_request, request) ) except TimeoutError as e: # retry_on raises a bare TimeoutError, not PanelException, once # its overall budget is exhausted primary_error: PanelException = PanelException( "Timed out obtaining document roots from the panel API" ) primary_error.__cause__ = e except PanelException as e: primary_error = e # the URL host is the panel's servername; when it does not resolve # back to this host (NAT, proxied DNS, firewalled port) the panel is # still reachable over loopback with the same one-time token, but # only over TLS -- the token must not be sent to a peer we cannot # authenticate (a local user could hijack a plaintext loopback port) if parsed_url.scheme != "https": raise primary_error logger.warning( "Panel API document roots request failed (%s)," " retrying over loopback", primary_error, ) try: return _valid_docroots_payload( await loop.run_in_executor( None, self._do_loopback_request, request ) ) except PanelException: raise primary_error async def docroots_info_legacy(self) -> Dict: cmd = [ "/usr/local/directadmin/directadmin", "--DocumentRoot", ] with timeit("Call DA binary to obtain all docroots", logger): ret, out, err = await run(cmd) if ret != 0 and ret != 1: raise PanelException( "Failed to obtain document roots. Unexpected return code" f" {ret}. stdout={out!r}, stderr={err!r}" ) try: output = json.loads(out.decode()) except json.JSONDecodeError as e: raise PanelException( f"Failed to obtain document roots. Failed to decode json {e}." ) return _valid_docroots_payload(output) @staticmethod def parse_document_root_output(output) -> Dict: ret = dict() for username, userdata in output["users"].items(): for domainname, domaindata in userdata["domains"].items(): if domaindata.get("public_html"): ret[domaindata["public_html"]] = domainname for _, sub_data in domaindata.get("subdomains", {}).items(): if sub_data.get("public_html"): ret[sub_data["public_html"]] = domainname return ret async def list_docroots(self) -> Dict[str, str]: info = await self.docroots_info() return self.parse_document_root_output(info) async def get_user_details(self) -> Dict[str, Dict[str, str]]: res = {} usernames = await self.get_users() admins = set(self._get_admins()) resellers = set(self._get_resellers()) for username in usernames: try: parsed_config = self.get_user_details_for_username(username) level = base.UserLevel.REGULAR_USER if username in resellers: level = base.UserLevel.RESSELER if username in admins: level = base.UserLevel.ADMIN res[username] = { "locale": parsed_config.get("language", ""), "email": parsed_config.get("email", ""), "parent": parsed_config.get("creator", ""), "suspended": parsed_config.get("suspended") == "yes", "level": int(level), } except Exception as e: res[username] = { "email": "", "locale": "", } logger.warning( "Failed to get_user_details: %s %s", username, e ) return res def get_user_details_for_username(self, username) -> Dict[str, str]: """ Implementation taken from https://github.com/patchman-cloudlinux/patchman2-client/blob/05f54db63639b939c055a9543e82bc9690559965/src/platform/platforms/directadmin.cpp#L315 directadmin::get_user_details """ user_conf_str = Path( USERS_CONF_DIR, f"{username}/user.conf" ).read_text() parsed_config = configparser.ConfigParser() user_conf_str = "[top]\n" + user_conf_str parsed_config.read_string(user_conf_str) parsed_config = parsed_config["top"] return parsed_config @retry_on( PanelException, on_error=backoff_sleep, timeout=HTTP_REQUEST_RETRY_TIMEOUT, ) def _do_request(self, request: urllib.request.Request) -> Any: return self._fetch_json(request, urllib.request.urlopen) @staticmethod def _fetch_json(request: urllib.request.Request, open_func) -> Any: try: with open_func( request, timeout=Core.DEFAULT_SOCKET_TIMEOUT ) as response: if response.status != 200: raise PanelException( "status code is {}".format(response.status) ) return json.loads(response.read().decode()) except ( UnicodeDecodeError, http.client.HTTPException, json.JSONDecodeError, socket.timeout, urllib.error.URLError, ) as e: raise PanelException from e def _do_loopback_request(self, request: urllib.request.Request) -> Any: transport = UrlTransport( ip_chooser=_LoopbackIpChooser(), use_proxies=False ) return self._fetch_json(request, transport.open) async def panel_user_link(self, username) -> str: """ Returns panel url :return: str """ return "" @async_lru_cache(maxsize=1, ttl=60) async def _get_domains_details_per_user( self, ) -> Dict[str, List[base.DomainData]]: info = await self.docroots_info() result: Dict[str, List[base.DomainData]] = defaultdict(list) for user, userdata in info.get("users", {}).items(): for domain, domain_data in userdata.get("domains", {}).items(): public_html = domain_data.get("public_html") if public_html: result[user].append( base.DomainData( docroot=public_html, domain=domain, type="main", username=user, ) ) for sub, sub_data in ( domain_data.get("subdomains") or {} ).items(): sub_public_html = sub_data.get("public_html") if sub_public_html: result[user].append( base.DomainData( docroot=sub_public_html, domain=f"{sub}.{domain}", type="sub", username=user, ) ) return result async def get_user_domains_details( self, username: str ) -> list[base.DomainData]: details = await self._get_domains_details_per_user() return list(details.get(username, [])) defence360agent/subsys/panels/generic/0000755000000000000000000000000000000000000014774 5ustar defence360agent/subsys/panels/generic/__init__.py0000644000000000000000000000007400000000000017106 0ustar from .panel import GenericPanel __all__ = ["GenericPanel"] defence360agent/subsys/panels/generic/__pycache__/0000755000000000000000000000000000000000000017204 5ustar defence360agent/subsys/panels/generic/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000044200000000000024404 0ustar r_j<ddlmZdgZdS)) GenericPanelrN)panelr__all__c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/__init__.pyr s#  rdefence360agent/subsys/panels/generic/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000044200000000000023445 0ustar r_j<ddlmZdgZdS)) GenericPanelrN)panelr__all__c/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/generic/__init__.pyr s#  rdefence360agent/subsys/panels/generic/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000005401100000000000023745 0ustar r_j.ddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z m Z ddl Z ddlZddlmZmZddlmZddlmZddlmZddlmZdd lmZmZmZmZd d lm Z ej!e"Z#ej$%e&d zZ'd Z(dZ)ej*eddZ+ej,d de-de j.fdZ/dZ0dZ1ee+dde-fdZ2de-de-de e-fdZ3de-de-fdZ4dZ5dZ6d Z7de e-fd!Z8Gd"d#e j9Z:Gd$d%e j;Zz*get_users_default_impl..:s% C C CbDbj ! ! ! C C C)rr2r'get_users_default_implr49s C C,@,B,B C C CCr2cf|}d|vr||dvr|d|SdS)Nintegration_scripts)to_dict)clrds r'_get_conf_pathr:=s@ A!!f2G0H&H&H&'// 4r2 ) expirationrcKtt|}|stt|}|std|zt ||d{VS)Nzl%s not found neither in /etc/sysconfig/imunify360/integration.conf nor in /opt/cpvendor/etc/integration.ini.)r:rrIntegrationScriptError_get_integration_data)rpaths r'get_integration_datarADs +--v 6 6D =133V<<  $ 8:@ A   'vt44 4 4 4 4 4 44r2r@c@|r|std|z tj|}n'#t$r}td|d|d}~wwxYw|std|z|d}t j|std|d|t j|std|d |t j |t j std|d ||S) z?Tokenize a config-supplied script command into argv (no shell).z$Empty integration script path for %sz$Invalid integration script path for z: NrzIntegration script path for z must be absolute: zIntegration script for z does not exist: z is not executable: ) stripr>shlexsplit ValueErrorosr@isabsisfileaccessX_OK)rr@argve executables r'_build_integration_argvrOSsv  tzz|| $ 2V ;    {4      $$>* % % $$vvzz #    9Z ) ) $$vvzz #    Ks? A# AA#cKt||} t|d{V}n6#t$r)}td||d}~wwxYw t j|}n1#tt j f$r}td|z|d}~wwxYwt|tstd|ztt}||std|d|j|tddkrJ|td}d |tvr|d |td zz }t|t|}||std |d|j|d S) NzMIntegrations script {script} failed with exit code {e.returncode} {e.stderr})rrMz"Cannot decode output of %s as JSONz%s should return dictz Validation error in metadata of z script: resultokmessagez: %szValidation error in data)rOrrr>rjsonloadsdecodeUnicodeDecodeErrorJSONDecodeError isinstancer-r(r"validateerrors) rr@rLstdoutrMrTmetadata_validatormetadata_error validators r'r?r?xs "64 0 0D  &&&&&&    $ v33    z&--//**  4 5$ 04 7    dD ! !E$%.s$DDDdaq))DDDr2z:Applying default implementation of users and domains lists) get_users_integration_datarAitemsget setdefaultappendr>loggerwarningr4)usersrbrfrg user_domainss r'_get_client_datarqs  (022222222,Y77777777MMOO ' 'DAq 'QUU7^^ '$//' B?? ##A&&&DDekkmmDDDD !((( H   &''''' (sB(B--2C"!C"cKtdd{V}i}|D]B}|r|dstd|7g||d<C|S)Nrousernamez#Found user with an empty username: )rArjrmrn)ro users_dictusers r'rhrhs&w// / / / / / /EJ.. .488J// . NNGGG H H H H+-JtJ' ( ( r2cK tdd{VS#t$rtdicYSwxYw)NrbzCould not parse domains lists)rAr>rmrnr3r2r'get_domain_datarwsb))444444444 !6777 s&AAc Kd}dh}tjt|tt |t|tt |dd{V}d|D}|st d|||z} tt5}| | dddn #1swxYwYn0#t$r#t dtYnwxYwt|S)NadminsrootT)return_exceptionscRh|]$}t|t|D] }|d %Sr+)rZlist)r/ryadmins r' z!get_admin_list..sW  fd # #   f r2zDError occurred during extracting admins from integration configs: %sz&Failed to retrieve admins list from %s)asynciogatherr?r:rrrmrnrADMIN_LIST_FILE_PATHupdateread splitlinesOSErrorr}) script_name admins_setadmins_from_integration_scripts custom_adminsadmin_list_files r'get_admin_listrsKJ,3N  !##       #%%    ---''''''#"5M   + +   -J & ' ' C?   o2244??AA B B B C C C C C C C C C C C C C C C     46J         s6$D 8:C>2 D >DD DD *D76D7ceZdZfdZxZS)r>chtj|t|dSN)super__init__rmrn)selfargskwargs __class__s r'rzIntegrationScriptError.__init__s/$tr2)__name__ __module__ __qualname__r __classcell__rs@r'r>r>s8r2r>ceZdZdZejZeZe dZ ddZ ddZ e dZ e dZdZd eefd Zd eeeeffd Zd eeeeeffffd Zd eeeeffd ZdefdZd eefdZd eeeffdZd eeeeffdZd efdZe ded eejfdZ xZ!S) GenericPanelzb Panel, UI to which is provided by imunify{-antivirus,360-firewall}-generic.{rpm,deb} ctSrr )clss r' is_installedzGenericPanel.is_installeds)+++r2Nc KdSrr3)rr,s r'enable_imunify_pluginz"GenericPanel.enable_imunify_plugin  r2c KdSrr3)r plugin_names r'disable_imunify_pluginz#GenericPanel.disable_imunify_plugin rr2cnK tdd{V}djdi|S#t$rYdSwxYw)N panel_infoz{name} {version}0r3)rArr>rinfos r'versionzGenericPanel.versionsf -l;;;;;;;;D,%,44t44 4%   33 s !& 44czK tdd{V}djdi|S#t$r |jcYSwxYw)Nrz{name}r3)rArr>NAMErs r'r,zGenericPanel.namesh -l;;;;;;;;D"8?**T** *%   8OOO s !&::cKtd{V}g}|D]7}||dt8|S)Nrb)rqextendrjtuple)rrorQrus r'get_user_domainszGenericPanel.get_user_domainssc&(((((((( 8 8D MM$((9egg66 7 7 7 7 r2rcFKtd{V}d|DS)Ncg|] }|d Sr+r3r/rus r'r1z*GenericPanel.get_users..'s///V ///r2rqrros r' get_userszGenericPanel.get_users%s7&((((((((//////r2cKtd{V}tt}|D]<}|dgD]#}|||d$=|S)Nrbr,)rqrr}rjrl)rrorQrudomains r'get_domain_to_ownerz GenericPanel.get_domain_to_owner)s&((((((((T"" 4 4D((9b11 4 4v%%d6l3333 4 r2c<K tdd{V}n6#t$r)td{VcYSwxYw|d{Vt d{Vdt ffd fd|DS)Nro user_infoc|ddkrtjjS|dvrtjjStjjS)Nrsrz)rjr UserLevelADMINRESSELER REGULAR_USER)rrys r'user_info_to_levelz9GenericPanel.get_user_details..user_info_to_level9sR}}Z((F22~++}}Z((F22~..>. .r2c i|]s}|ro|dv|d|dd|ddt|dtS)rsemail locale_code)rlocalelevel)rjint)r/rr usernamess r' z1GenericPanel.get_user_details..Bs    ,, 99 HHZ '2..((="55//5566## :99r2)rAr>rget_user_detailsrrr)r user_dataryrrrs @@@r'rzGenericPanel.get_user_details1s 427;;;;;;;;II% 4 4 41133333333 3 3 3 4..******** %'''''''' /$ / / / / / /     "     s0AAcFKtd{V}d|DS)NcHi|]}|d|dg Srerjrs r'rz5GenericPanel.get_domains_per_user..Os,HHH$V dhhy"55HHHr2rrs r'get_domains_per_userz!GenericPanel.get_domains_per_userLs7&((((((((HH%HHHHr2rTc|jdkrf|dddgkrX|ddd}tj|}|d|dtjkr|dndfS|jdfS) Nrcommandloginpamparamsjwt user_type user_name)_uidpopr parse_tokenr NON_ROOTru)rprotocolrTtoken parsed_tokens r' authenticatezGenericPanel.authenticateQs =A  $y/gu5E"E"EN&&ud33E$077L , ,0AAA[))  =$& &r2ct}d|vr7d|dvr-t|ddStS)Nmalwarebasedir)rr7setrE)rconfs r'basedirszGenericPanel.basedirs]s^ ""**,,   d9o!=!=tIy17799:: :uu r2cjKtd{V}d|DS)NcTi|]%\}}||d|d|&S document_rootrr/rrgs r'rz.GenericPanel.list_docroots..esO    UU?++ o    r2rwrirrbs r' list_docrootszGenericPanel.list_docrootscL'))))))))  $]]__    r2cjKtd{V}d|DS)NcVi|]&\}}||d||dg'Srrrs r'rz1GenericPanel.get_domain_paths..msR    UU?++ Q'(   r2rrs r'get_domain_pathszGenericPanel.get_domain_pathskrr2c KdS)z8 Returns panel url :return: str rr3)rrss r'panel_user_linkzGenericPanel.panel_user_linkss rr2rscpKtd{V}fd|DS)Nc g|]d\}}|r]|dk tj|dpd||drdndeS)rcrris_mainmainaddon)docrootrtypers)rjr DomainData)r/rrgrss r'r1z9GenericPanel.get_user_domains_details..s      UU7^^x// Oo..4" uuY// exception classmethodrrrrr,rrstrrrrrrr-rrrrrrr}rrrrs@r'rrs@  "D&I,,[,        [[0c00004T#Y+? S$sCx.-@(A      6IDd3i,@IIII '4 ' ' ' '#c(  T#s(^     S$s)^(<        do     [      r2r)=rdatetime functoolsrUloggingrGrD collectionsrtypingrrrr#r $defence360agent.api.integration_confrrdefence360agent.api.jwt_issuerr 3defence360agent.application.determine_hosting_panelr defence360agent.contracts.configr defence360agent.rpc_tools.lookupr defence360agent.utilsrrrrrr getLoggerrrmr@dirname__file__rrr" timedelta'EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS lru_cacherr$r(r4r:rArOr?rqrhrwrPanelExceptionr> AbstractPanelrr3r2r'rsj  ######"""""""""" 544444=<<<<<555555  8 $ $GOOH FF> *<(*< O7  +++'Q*3*8#5*** *DDD ?LLL 5s 5 5 5ML 5"C"s"tCy""""J''3''''T((("   *d3i****ZT0 M M M M M 4%M M M M M r2defence360agent/subsys/panels/generic/__pycache__/panel.cpython-311.pyc0000644000000000000000000005401100000000000023006 0ustar r_j.ddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z m Z ddl Z ddlZddlmZmZddlmZddlmZddlmZddlmZdd lmZmZmZmZd d lm Z ej!e"Z#ej$%e&d zZ'd Z(dZ)ej*eddZ+ej,d de-de j.fdZ/dZ0dZ1ee+dde-fdZ2de-de-de e-fdZ3de-de-fdZ4dZ5dZ6d Z7de e-fd!Z8Gd"d#e j9Z:Gd$d%e j;Zz*get_users_default_impl..:s% C C CbDbj ! ! ! C C C)rr2r'get_users_default_implr49s C C,@,B,B C C CCr2cf|}d|vr||dvr|d|SdS)Nintegration_scripts)to_dict)clrds r'_get_conf_pathr:=s@ A!!f2G0H&H&H&'// 4r2 ) expirationrcKtt|}|stt|}|std|zt ||d{VS)Nzl%s not found neither in /etc/sysconfig/imunify360/integration.conf nor in /opt/cpvendor/etc/integration.ini.)r:rrIntegrationScriptError_get_integration_data)rpaths r'get_integration_datarADs +--v 6 6D =133V<<  $ 8:@ A   'vt44 4 4 4 4 4 44r2r@c@|r|std|z tj|}n'#t$r}td|d|d}~wwxYw|std|z|d}t j|std|d|t j|std|d |t j |t j std|d ||S) z?Tokenize a config-supplied script command into argv (no shell).z$Empty integration script path for %sz$Invalid integration script path for z: NrzIntegration script path for z must be absolute: zIntegration script for z does not exist: z is not executable: ) stripr>shlexsplit ValueErrorosr@isabsisfileaccessX_OK)rr@argve executables r'_build_integration_argvrOSsv  tzz|| $ 2V ;    {4      $$>* % % $$vvzz #    9Z ) ) $$vvzz #    Ks? A# AA#cKt||} t|d{V}n6#t$r)}td||d}~wwxYw t j|}n1#tt j f$r}td|z|d}~wwxYwt|tstd|ztt}||std|d|j|tddkrJ|td}d |tvr|d |td zz }t|t|}||std |d|j|d S) NzMIntegrations script {script} failed with exit code {e.returncode} {e.stderr})rrMz"Cannot decode output of %s as JSONz%s should return dictz Validation error in metadata of z script: resultokmessagez: %szValidation error in data)rOrrr>rjsonloadsdecodeUnicodeDecodeErrorJSONDecodeError isinstancer-r(r"validateerrors) rr@rLstdoutrMrTmetadata_validatormetadata_error validators r'r?r?xs "64 0 0D  &&&&&&    $ v33    z&--//**  4 5$ 04 7    dD ! !E$%.s$DDDdaq))DDDr2z:Applying default implementation of users and domains lists) get_users_integration_datarAitemsget setdefaultappendr>loggerwarningr4)usersrbrfrg user_domainss r'_get_client_datarqs  (022222222,Y77777777MMOO ' 'DAq 'QUU7^^ '$//' B?? ##A&&&DDekkmmDDDD !((( H   &''''' (sB(B--2C"!C"cKtdd{V}i}|D]B}|r|dstd|7g||d<C|S)Nrousernamez#Found user with an empty username: )rArjrmrn)ro users_dictusers r'rhrhs&w// / / / / / /EJ.. .488J// . NNGGG H H H H+-JtJ' ( ( r2cK tdd{VS#t$rtdicYSwxYw)NrbzCould not parse domains lists)rAr>rmrnr3r2r'get_domain_datarwsb))444444444 !6777 s&AAc Kd}dh}tjt|tt |t|tt |dd{V}d|D}|st d|||z} tt5}| | dddn #1swxYwYn0#t$r#t dtYnwxYwt|S)NadminsrootT)return_exceptionscRh|]$}t|t|D] }|d %Sr+)rZlist)r/ryadmins r' z!get_admin_list..sW  fd # #   f r2zDError occurred during extracting admins from integration configs: %sz&Failed to retrieve admins list from %s)asynciogatherr?r:rrrmrnrADMIN_LIST_FILE_PATHupdateread splitlinesOSErrorr}) script_name admins_setadmins_from_integration_scripts custom_adminsadmin_list_files r'get_admin_listrsKJ,3N  !##       #%%    ---''''''#"5M   + +   -J & ' ' C?   o2244??AA B B B C C C C C C C C C C C C C C C     46J         s6$D 8:C>2 D >DD DD *D76D7ceZdZfdZxZS)r>chtj|t|dSN)super__init__rmrn)selfargskwargs __class__s r'rzIntegrationScriptError.__init__s/$tr2)__name__ __module__ __qualname__r __classcell__rs@r'r>r>s8r2r>ceZdZdZejZeZe dZ ddZ ddZ e dZ e dZdZd eefd Zd eeeeffd Zd eeeeeffffd Zd eeeeffd ZdefdZd eefdZd eeeffdZd eeeeffdZd efdZe ded eejfdZ xZ!S) GenericPanelzb Panel, UI to which is provided by imunify{-antivirus,360-firewall}-generic.{rpm,deb} ctSrr )clss r' is_installedzGenericPanel.is_installeds)+++r2Nc KdSrr3)rr,s r'enable_imunify_pluginz"GenericPanel.enable_imunify_plugin  r2c KdSrr3)r plugin_names r'disable_imunify_pluginz#GenericPanel.disable_imunify_plugin rr2cnK tdd{V}djdi|S#t$rYdSwxYw)N panel_infoz{name} {version}0r3)rArr>rinfos r'versionzGenericPanel.versionsf -l;;;;;;;;D,%,44t44 4%   33 s !& 44czK tdd{V}djdi|S#t$r |jcYSwxYw)Nrz{name}r3)rArr>NAMErs r'r,zGenericPanel.namesh -l;;;;;;;;D"8?**T** *%   8OOO s !&::cKtd{V}g}|D]7}||dt8|S)Nrb)rqextendrjtuple)rrorQrus r'get_user_domainszGenericPanel.get_user_domainssc&(((((((( 8 8D MM$((9egg66 7 7 7 7 r2rcFKtd{V}d|DS)Ncg|] }|d Sr+r3r/rus r'r1z*GenericPanel.get_users..'s///V ///r2rqrros r' get_userszGenericPanel.get_users%s7&((((((((//////r2cKtd{V}tt}|D]<}|dgD]#}|||d$=|S)Nrbr,)rqrr}rjrl)rrorQrudomains r'get_domain_to_ownerz GenericPanel.get_domain_to_owner)s&((((((((T"" 4 4D((9b11 4 4v%%d6l3333 4 r2c<K tdd{V}n6#t$r)td{VcYSwxYw|d{Vt d{Vdt ffd fd|DS)Nro user_infoc|ddkrtjjS|dvrtjjStjjS)Nrsrz)rjr UserLevelADMINRESSELER REGULAR_USER)rrys r'user_info_to_levelz9GenericPanel.get_user_details..user_info_to_level9sR}}Z((F22~++}}Z((F22~..>. .r2c i|]s}|ro|dv|d|dd|ddt|dtS)rsemail locale_code)rlocalelevel)rjint)r/rr usernamess r' z1GenericPanel.get_user_details..Bs    ,, 99 HHZ '2..((="55//5566## :99r2)rAr>rget_user_detailsrrr)r user_dataryrrrs @@@r'rzGenericPanel.get_user_details1s 427;;;;;;;;II% 4 4 41133333333 3 3 3 4..******** %'''''''' /$ / / / / / /     "     s0AAcFKtd{V}d|DS)NcHi|]}|d|dg Srerjrs r'rz5GenericPanel.get_domains_per_user..Os,HHH$V dhhy"55HHHr2rrs r'get_domains_per_userz!GenericPanel.get_domains_per_userLs7&((((((((HH%HHHHr2rTc|jdkrf|dddgkrX|ddd}tj|}|d|dtjkr|dndfS|jdfS) Nrcommandloginpamparamsjwt user_type user_name)_uidpopr parse_tokenr NON_ROOTru)rprotocolrTtoken parsed_tokens r' authenticatezGenericPanel.authenticateQs =A  $y/gu5E"E"EN&&ud33E$077L , ,0AAA[))  =$& &r2ct}d|vr7d|dvr-t|ddStS)Nmalwarebasedir)rr7setrE)rconfs r'basedirszGenericPanel.basedirs]s^ ""**,,   d9o!=!=tIy17799:: :uu r2cjKtd{V}d|DS)NcTi|]%\}}||d|d|&S document_rootrr/rrgs r'rz.GenericPanel.list_docroots..esO    UU?++ o    r2rwrirrbs r' list_docrootszGenericPanel.list_docrootscL'))))))))  $]]__    r2cjKtd{V}d|DS)NcVi|]&\}}||d||dg'Srrrs r'rz1GenericPanel.get_domain_paths..msR    UU?++ Q'(   r2rrs r'get_domain_pathszGenericPanel.get_domain_pathskrr2c KdS)z8 Returns panel url :return: str rr3)rrss r'panel_user_linkzGenericPanel.panel_user_linkss rr2rscpKtd{V}fd|DS)Nc g|]d\}}|r]|dk tj|dpd||drdndeS)rcrris_mainmainaddon)docrootrtypers)rjr DomainData)r/rrgrss r'r1z9GenericPanel.get_user_domains_details..s      UU7^^x// Oo..4" uuY// exception classmethodrrrrr,rrstrrrrrrr-rrrrrrr}rrrrs@r'rrs@  "D&I,,[,        [[0c00004T#Y+? S$sCx.-@(A      6IDd3i,@IIII '4 ' ' ' '#c(  T#s(^     S$s)^(<        do     [      r2r)=rdatetime functoolsrUloggingrGrD collectionsrtypingrrrr#r $defence360agent.api.integration_confrrdefence360agent.api.jwt_issuerr 3defence360agent.application.determine_hosting_panelr defence360agent.contracts.configr defence360agent.rpc_tools.lookupr defence360agent.utilsrrrrrr getLoggerrrmr@dirname__file__rrr" timedelta'EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS lru_cacherr$r(r4r:rArOr?rqrhrwrPanelExceptionr> AbstractPanelrr3r2r'rsj  ######"""""""""" 544444=<<<<<555555  8 $ $GOOH FF> *<(*< O7  +++'Q*3*8#5*** *DDD ?LLL 5s 5 5 5ML 5"C"s"tCy""""J''3''''T((("   *d3i****ZT0 M M M M M 4%M M M M M r2defence360agent/subsys/panels/generic/panel.py0000644000000000000000000002737200000000000016460 0ustar import asyncio import datetime import functools import json import logging import os import shlex from collections import defaultdict from typing import Dict, List, Set import cerberus import yaml from defence360agent.api.integration_conf import ( ClIntegrationConfig, IntegrationConfig, ) from defence360agent.api.jwt_issuer import JWTIssuer from defence360agent.application.determine_hosting_panel import ( is_generic_panel_installed, ) from defence360agent.contracts.config import int_from_envvar from defence360agent.rpc_tools.lookup import UserType from defence360agent.utils import ( CheckRunError, check_run, get_non_system_users, timed_cache, ) from .. import base logger = logging.getLogger(__name__) _SCHEMA_PATH_TMPL = ( os.path.dirname(__file__) + "/users_script_schemas/schema-{}.yaml" ) ADMIN_LIST_FILE_PATH = "/etc/sysconfig/imunify360/auth.admin" METADATA = "metadata" EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS = datetime.timedelta( seconds=int_from_envvar( "IMUNIFY360_EXPIRATION_FOR_INTEGRATION_SCRIPTS", 90 ) ) @functools.lru_cache(maxsize=2) def _get_validator(script: str) -> cerberus.Validator: """Returns a validator for given script.""" with open(_SCHEMA_PATH_TMPL.format(script)) as schema_file: schema = yaml.safe_load(schema_file) if script is not METADATA: schema[METADATA] = {"required": True} return cerberus.Validator(schema) def get_users_default_impl(): return [dict(name=pw.pw_name) for pw in get_non_system_users()] def _get_conf_path(cl, script): d = cl.to_dict() if "integration_scripts" in d and script in d["integration_scripts"]: return d["integration_scripts"][script] return None @timed_cache(expiration=EXPIRATION_TIME_FOR_INTEGRATION_SCRIPTS, maxsize=10) async def get_integration_data(script: str): path = _get_conf_path(IntegrationConfig(), script) if not path: path = _get_conf_path(ClIntegrationConfig(), script) if not path: raise IntegrationScriptError( "%s not found neither in " "/etc/sysconfig/imunify360/integration.conf " "nor in /opt/cpvendor/etc/integration.ini." % script ) return await _get_integration_data(script, path) def _build_integration_argv(script: str, path: str) -> List[str]: """Tokenize a config-supplied script command into argv (no shell).""" if not path or not path.strip(): raise IntegrationScriptError( "Empty integration script path for %s" % script ) try: argv = shlex.split(path) except ValueError as e: raise IntegrationScriptError( "Invalid integration script path for %s: %s" % (script, e) ) if not argv: raise IntegrationScriptError( "Empty integration script path for %s" % script ) executable = argv[0] if not os.path.isabs(executable): raise IntegrationScriptError( "Integration script path for %s must be absolute: %s" % (script, executable) ) if not os.path.isfile(executable): raise IntegrationScriptError( "Integration script for %s does not exist: %s" % (script, executable) ) if not os.access(executable, os.X_OK): raise IntegrationScriptError( "Integration script for %s is not executable: %s" % (script, executable) ) return argv async def _get_integration_data(script: str, path: str): argv = _build_integration_argv(script, path) try: stdout = await check_run(argv) except CheckRunError as e: raise IntegrationScriptError( "Integrations script {script} " "failed with exit code {e.returncode} \n" "{e.stderr}".format(script=script, e=e) ) try: data = json.loads(stdout.decode()) except (UnicodeDecodeError, json.JSONDecodeError) as e: raise IntegrationScriptError( "Cannot decode output of %s as JSON" % path ) from e if not isinstance(data, dict): raise IntegrationScriptError("%s should return dict" % path) metadata_validator = _get_validator(METADATA) if not metadata_validator.validate(data): raise IntegrationScriptError( "Validation error in metadata of %s script: %s" % (script, metadata_validator.errors) ) if data[METADATA]["result"] != "ok": metadata_error = data[METADATA]["result"] if "message" in data[METADATA]: metadata_error += ": %s" % data[METADATA]["message"] raise IntegrationScriptError(metadata_error) validator = _get_validator(script) if not validator.validate(data): raise IntegrationScriptError( "Validation error in %s script: %s" % (script, validator.errors) ) return data["data"] async def _get_client_data(): try: users = await get_users_integration_data() domains = await get_integration_data("domains") for k, v in domains.items(): if v and v.get("owner"): user_domains = users.setdefault(v["owner"], []) user_domains.append(k) return [{"name": k, "domains": v} for k, v in users.items()] except IntegrationScriptError: logger.warning( "Applying default implementation of users and domains lists" ) return get_users_default_impl() async def get_users_integration_data(): users = await get_integration_data("users") users_dict = {} for user in users: if not user or not user.get("username"): logger.warning(f"Found user with an empty username: {user}") else: users_dict[user["username"]] = [] return users_dict async def get_domain_data(): try: return await get_integration_data("domains") except IntegrationScriptError: logger.warning("Could not parse domains lists") return {} async def get_admin_list() -> List[str]: script_name = "admins" admins_set = {"root"} admins_from_integration_scripts = await asyncio.gather( _get_integration_data( script_name, _get_conf_path( IntegrationConfig(), script_name, ), ), _get_integration_data( script_name, _get_conf_path( ClIntegrationConfig(), script_name, ), ), return_exceptions=True, ) custom_admins = { admin["name"] for admins in admins_from_integration_scripts if isinstance(admins, list) # skip exceptions for admin in admins } if not custom_admins: logger.warning( "Error occurred during extracting admins " "from integration configs: %s", admins_from_integration_scripts, ) admins_set |= custom_admins try: with open(ADMIN_LIST_FILE_PATH) as admin_list_file: admins_set.update(admin_list_file.read().splitlines()) except OSError: logger.warning( "Failed to retrieve admins list from %s", ADMIN_LIST_FILE_PATH ) return list(admins_set) class IntegrationScriptError(base.PanelException): def __init__(self, *args, **kwargs): super().__init__(*args) logger.warning(self) class GenericPanel(base.AbstractPanel): """ Panel, UI to which is provided by imunify{-antivirus,360-firewall}-generic.{rpm,deb} """ NAME = base.GENERIC_PANEL_NAME exception = IntegrationScriptError @classmethod def is_installed(cls): return is_generic_panel_installed() # pragma: no cover async def enable_imunify_plugin(self, name=None): pass async def disable_imunify_plugin(self, plugin_name=None): pass @classmethod async def version(cls): try: info = await get_integration_data("panel_info") return "{name} {version}".format(**info) except IntegrationScriptError: return "0" @classmethod async def name(cls): try: info = await get_integration_data("panel_info") return "{name}".format(**info) except IntegrationScriptError: return cls.NAME async def get_user_domains(self): users = await _get_client_data() result = [] for user in users: result.extend(user.get("domains", tuple())) return result async def get_users(self) -> List[str]: users = await _get_client_data() return [user["name"] for user in users] async def get_domain_to_owner(self) -> Dict[str, List[str]]: users = await _get_client_data() result = defaultdict(list) for user in users: for domain in user.get("domains", []): result[domain].append(user["name"]) return result async def get_user_details(self) -> Dict[str, Dict[str, str]]: try: user_data = await get_integration_data("users") except IntegrationScriptError: return await super().get_user_details() usernames = await self.get_users() admins = await get_admin_list() def user_info_to_level(user_info: Dict): if user_info.get("username") == "root": return base.UserLevel.ADMIN if user_info.get("username") in admins: return base.UserLevel.RESSELER return base.UserLevel.REGULAR_USER return { info.get("username"): { "email": info.get("email", ""), "locale": info.get("locale_code", ""), "level": int(user_info_to_level(info)), } for info in user_data if info and info.get("username") in usernames } async def get_domains_per_user(self) -> Dict[str, List[str]]: users = await _get_client_data() return {user["name"]: user.get("domains", []) for user in users} def authenticate(self, protocol, data: dict): if protocol._uid != 0 and data["command"] != ["login", "pam"]: token = data["params"].pop("jwt", None) parsed_token = JWTIssuer.parse_token(token) return parsed_token["user_type"], ( parsed_token["user_name"] if parsed_token["user_type"] == UserType.NON_ROOT else None ) else: return protocol.user, None def basedirs(self) -> Set[str]: conf = IntegrationConfig().to_dict() if "malware" in conf and "basedir" in conf["malware"]: return set(conf["malware"]["basedir"].split()) return set() async def list_docroots(self) -> Dict[str, str]: domains = await get_domain_data() return { v["document_root"]: domain for domain, v in domains.items() if v and v.get("document_root") } async def get_domain_paths(self) -> Dict[str, List[str]]: domains = await get_domain_data() return { domain: [v["document_root"]] for domain, v in domains.items() if v and v.get("document_root") } async def panel_user_link(self, username) -> str: """ Returns panel url :return: str """ return "" @classmethod async def get_user_domains_details( cls, username: str ) -> list[base.DomainData]: domains = await get_domain_data() return [ base.DomainData( docroot=v.get("document_root") or "", domain=domain, type="main" if v.get("is_main") else "addon", username=username, ) for domain, v in domains.items() if v and v.get("owner") == username ] defence360agent/subsys/panels/generic/users_script_schemas/0000755000000000000000000000000000000000000021224 5ustar defence360agent/subsys/panels/generic/users_script_schemas/schema-admins.yaml0000644000000000000000000000046000000000000024621 0ustar # Cerberus (http://docs.python-cerberus.org/en/stable/) schema for site # information API data: type: list required: true schema: type: dict allow_unknown: true schema: name: type: string required: true is_main: type: boolean required: true defence360agent/subsys/panels/generic/users_script_schemas/schema-domains.yaml0000644000000000000000000000052200000000000024777 0ustar # Cerberus (http://docs.python-cerberus.org/en/stable/) schema for site # information API data: type: dict required: false valuesrules: type: dict required: true nullable: true allow_unknown: true schema: owner: type: string required: true keysrules: type: string required: true defence360agent/subsys/panels/generic/users_script_schemas/schema-metadata.yaml0000644000000000000000000000043700000000000025132 0ustar # Cerberus (http://docs.python-cerberus.org/en/stable/) schema for site # information API data: required: true nullable: true metadata: type: dict required: true schema: result: type: string required: true message: type: string required: false defence360agent/subsys/panels/generic/users_script_schemas/schema-panel_info.yaml0000644000000000000000000000040700000000000025461 0ustar # Cerberus (http://docs.python-cerberus.org/en/stable/) schema for site # information API data: type: dict allow_unknown: true schema: name: type: string required: true version: type: string required: true defence360agent/subsys/panels/generic/users_script_schemas/schema-users.yaml0000644000000000000000000000041300000000000024505 0ustar # Cerberus (http://docs.python-cerberus.org/en/stable/) schema for site # information API data: type: list required: true schema: type: dict nullable: true allow_unknown: true schema: username: type: string required: true defence360agent/subsys/panels/hosting_panel.py0000644000000000000000000000203100000000000016560 0ustar from defence360agent.application.determine_hosting_panel import ( get_hosting_panel, ) from defence360agent.subsys.panels.base import AbstractPanel from defence360agent.utils import importer def _default_panel_root() -> str: """Use im360 panel classes when the im360 package is installed.""" if importer.exists("im360"): return "im360" return "defence360agent" _panel_root = _default_panel_root() panel = None def set_panel_root(root_module: str) -> None: global _panel_root, panel _panel_root = root_module panel = None # reset so next HostingPanel() call uses new root def HostingPanel(check_for_changes=False) -> AbstractPanel: """ Return the hosting panel singleton. Panels are loaded from ``_panel_root`` which auto-detects the correct package (``im360`` when installed, ``defence360agent`` otherwise). Can be overridden via ``set_panel_root``. """ global panel if panel is None or check_for_changes: panel = get_hosting_panel(_panel_root) return panel defence360agent/subsys/panels/no_cp/0000755000000000000000000000000000000000000014456 5ustar defence360agent/subsys/panels/no_cp/__init__.py0000644000000000000000000000005400000000000016566 0ustar from .panel import NoCP __all__ = ["NoCP"] defence360agent/subsys/panels/no_cp/__pycache__/0000755000000000000000000000000000000000000016666 5ustar defence360agent/subsys/panels/no_cp/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000042700000000000024071 0ustar r_j,ddlmZdgZdS))NoCPrN)panelr__all__a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/__init__.pyr s" (rdefence360agent/subsys/panels/no_cp/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000042700000000000023132 0ustar r_j,ddlmZdgZdS))NoCPrN)panelr__all__a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/__init__.pyr s" (rdefence360agent/subsys/panels/no_cp/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000001757100000000000023441 0ustar r_j5RddlZddlZddlZddlZddlmZddlmZmZm Z m Z ddl Z ddl Z ddl mZddlmZmZddlmZejeZdZejd d ed e jfd Zd e efd ZGddejZ Gddej!ZdS)N) defaultdict)DictListOptionalSet)NoCP)get_non_system_usersrun)basez;/opt/imunify360/venv/share/imunify360/no_cp/schema-v{}.yaml)maxsizeversionreturnctt|5}tj|}t j|cdddS#1swxYwYdS)z*Returns a validator for given API version.N)open_SCHEMA_PATH_TMPLformatyaml safe_loadcerberus Validator)r schema_fileschemas ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/panel.py_get_validatorrs &&w// 0 0*K ,,!&))******************s(AA!$A!cDK ttjttjgd{V}n=#t $rYdSt j$rt$r}td|d}~wwxYw|\}}}|dkr"td | tj | }n"#t$r}td|d}~wwxYw|d}|tdt|t r|dkr|t"jks"td |t%|}||}|std |j|S) z8Runs a script, validates its JSON output and returns it.Nzfailed to run scriptrzexited with code: {}zCannot decode output as JSONrz#output does not have`version` fieldzinvalid API version: {}zvalidation error: {})r Config CLIENT_SCRIPTstrLATEST_VERSIONFileNotFoundErrorasyncioCancelledError Exception ScriptErrorrjsonloadsdecodeget isinstanceintrrvalidateerrors) resultexccodestdout_datar validatoroks r_get_client_datar7s;F0#f6K2L2LMNNNNNNNN tt  ! ;;;011s:;OD&! qyy077==>>>Cz&--//** CCC899sBChhy!!G?@@@7C  E qLL t* * *3::7CCDDDw''I   D ! !B D0)2BCCC Ks38= A7 A7"A22A7)&C C/C**C/ceZdZdS)r&N)__name__ __module__ __qualname__rr&r&?sDr=r&c6eZdZdZeZedZddZddZ edZ dZ de e fd Zdee e e ffd Zdee e e ffd Zdee fd Zdee e ffd Zde fdZede deejfdZdS)rzno panelcdS)NFr<clss r is_installedzNoCP.is_installedGsur=Nc KdSNr<)selfnames renable_imunify_pluginzNoCP.enable_imunify_pluginK  r=c KdSrDr<)rE plugin_names rdisable_imunify_pluginzNoCP.disable_imunify_pluginNrHr=c KdS)N0r<r@s rrz NoCP.versionQs sr=cZKtd{V}|gSd|dDS)Ncg|] }|d S)rFr<.0domains r z)NoCP.get_user_domains..Ys===6v===r=domainsr7rEr4s rget_user_domainszNoCP.get_user_domainsUsE%'''''''' <I==T)_====r=rc6KdtDS)Ncg|] }|j Sr<)pw_name)rQpws rrSz"NoCP.get_users..\s<<z,NoCP.get_domain_to_owner..bs2   28F6NVG_-   r=rTrUrVs rget_domain_to_ownerzNoCP.get_domain_to_owner^sR%'''''''' <I  <@O    r=cKtd{V}|iStt}|dD])}||d|d*t |S)NrTr`rF)r7rlistappenddict)rEr4user_to_domainsrRs rget_domains_per_userzNoCP.get_domains_per_userfs%'''''''' <I%d++9o D DF F7O , 3 3F6N C C C CO$$$r=ctSrD)setr\s rbasedirsz NoCP.basedirsqs uu r=c"KtSrD)rfr\s r list_docrootszNoCP.list_docrootstsvv r=c KdS)z8 Returns panel url :return: str r<)rEusernames rpanel_user_linkzNoCP.panel_user_linkws rr=rpc KgSrDr<)rArps rget_user_domains_detailszNoCP.get_user_domains_details~s  r=rD)r9r:r;NAMEr& exception classmethodrBrGrKrrWrr r]rrbrhrrkrmrqrdr DomainDatarsr<r=rrrCs DI[        [>>> =c==== 4T#Y+?     %Dd3i,@ % % % %#c(T#s(^ do [r=r)"r# functoolsr'logging collectionsrtypingrrrrrr defence360agent.contracts.configrrdefence360agent.utilsr r ror getLoggerr9loggerr lru_cacher,rrrfr7PanelExceptionr& AbstractPanelr<r=rrs ######,,,,,,,,,,,, ;;;;;;;;;;;;;;  8 $ $A Q*C*H$6*** *B     $%   ?????4 ?????r=defence360agent/subsys/panels/no_cp/__pycache__/panel.cpython-311.pyc0000644000000000000000000001757100000000000022502 0ustar r_j5RddlZddlZddlZddlZddlmZddlmZmZm Z m Z ddl Z ddl Z ddl mZddlmZmZddlmZejeZdZejd d ed e jfd Zd e efd ZGddejZ Gddej!ZdS)N) defaultdict)DictListOptionalSet)NoCP)get_non_system_usersrun)basez;/opt/imunify360/venv/share/imunify360/no_cp/schema-v{}.yaml)maxsizeversionreturnctt|5}tj|}t j|cdddS#1swxYwYdS)z*Returns a validator for given API version.N)open_SCHEMA_PATH_TMPLformatyaml safe_loadcerberus Validator)r schema_fileschemas ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/no_cp/panel.py_get_validatorrs &&w// 0 0*K ,,!&))******************s(AA!$A!cDK ttjttjgd{V}n=#t $rYdSt j$rt$r}td|d}~wwxYw|\}}}|dkr"td | tj | }n"#t$r}td|d}~wwxYw|d}|tdt|t r|dkr|t"jks"td |t%|}||}|std |j|S) z8Runs a script, validates its JSON output and returns it.Nzfailed to run scriptrzexited with code: {}zCannot decode output as JSONrz#output does not have`version` fieldzinvalid API version: {}zvalidation error: {})r Config CLIENT_SCRIPTstrLATEST_VERSIONFileNotFoundErrorasyncioCancelledError Exception ScriptErrorrjsonloadsdecodeget isinstanceintrrvalidateerrors) resultexccodestdout_datar validatoroks r_get_client_datar7s;F0#f6K2L2LMNNNNNNNN tt  ! ;;;011s:;OD&! qyy077==>>>Cz&--//** CCC899sBChhy!!G?@@@7C  E qLL t* * *3::7CCDDDw''I   D ! !B D0)2BCCC Ks38= A7 A7"A22A7)&C C/C**C/ceZdZdS)r&N)__name__ __module__ __qualname__rr&r&?sDr=r&c6eZdZdZeZedZddZddZ edZ dZ de e fd Zdee e e ffd Zdee e e ffd Zdee fd Zdee e ffd Zde fdZede deejfdZdS)rzno panelcdS)NFr<clss r is_installedzNoCP.is_installedGsur=Nc KdSNr<)selfnames renable_imunify_pluginzNoCP.enable_imunify_pluginK  r=c KdSrDr<)rE plugin_names rdisable_imunify_pluginzNoCP.disable_imunify_pluginNrHr=c KdS)N0r<r@s rrz NoCP.versionQs sr=cZKtd{V}|gSd|dDS)Ncg|] }|d S)rFr<.0domains r z)NoCP.get_user_domains..Ys===6v===r=domainsr7rEr4s rget_user_domainszNoCP.get_user_domainsUsE%'''''''' <I==T)_====r=rc6KdtDS)Ncg|] }|j Sr<)pw_name)rQpws rrSz"NoCP.get_users..\s<<z,NoCP.get_domain_to_owner..bs2   28F6NVG_-   r=rTrUrVs rget_domain_to_ownerzNoCP.get_domain_to_owner^sR%'''''''' <I  <@O    r=cKtd{V}|iStt}|dD])}||d|d*t |S)NrTr`rF)r7rlistappenddict)rEr4user_to_domainsrRs rget_domains_per_userzNoCP.get_domains_per_userfs%'''''''' <I%d++9o D DF F7O , 3 3F6N C C C CO$$$r=ctSrD)setr\s rbasedirsz NoCP.basedirsqs uu r=c"KtSrD)rfr\s r list_docrootszNoCP.list_docrootstsvv r=c KdS)z8 Returns panel url :return: str r<)rEusernames rpanel_user_linkzNoCP.panel_user_linkws rr=rpc KgSrDr<)rArps rget_user_domains_detailszNoCP.get_user_domains_details~s  r=rD)r9r:r;NAMEr& exception classmethodrBrGrKrrWrr r]rrbrhrrkrmrqrdr DomainDatarsr<r=rrrCs DI[        [>>> =c==== 4T#Y+?     %Dd3i,@ % % % %#c(T#s(^ do [r=r)"r# functoolsr'logging collectionsrtypingrrrrrr defence360agent.contracts.configrrdefence360agent.utilsr r ror getLoggerr9loggerr lru_cacher,rrrfr7PanelExceptionr& AbstractPanelr<r=rrs ######,,,,,,,,,,,, ;;;;;;;;;;;;;;  8 $ $A Q*C*H$6*** *B     $%   ?????4 ?????r=defence360agent/subsys/panels/no_cp/panel.py0000644000000000000000000000706500000000000016137 0ustar import asyncio import functools import json import logging from collections import defaultdict from typing import Dict, List, Optional, Set import cerberus import yaml from defence360agent.contracts.config import NoCP as Config from defence360agent.utils import get_non_system_users, run from .. import base logger = logging.getLogger(__name__) _SCHEMA_PATH_TMPL = ( "/opt/imunify360/venv/share/imunify360/no_cp/schema-v{}.yaml" ) @functools.lru_cache(maxsize=2) def _get_validator(version: int) -> cerberus.Validator: """Returns a validator for given API version.""" with open(_SCHEMA_PATH_TMPL.format(version)) as schema_file: schema = yaml.safe_load(schema_file) return cerberus.Validator(schema) async def _get_client_data() -> Optional[dict]: """Runs a script, validates its JSON output and returns it.""" try: result = await run([Config.CLIENT_SCRIPT, str(Config.LATEST_VERSION)]) except FileNotFoundError: return None except asyncio.CancelledError: raise except Exception as exc: raise ScriptError("failed to run script") from exc code, stdout, _ = result if code != 0: raise ScriptError("exited with code: {}".format(code)) try: data = json.loads(stdout.decode()) except Exception as exc: raise ScriptError("Cannot decode output as JSON") from exc version = data.get("version") if version is None: raise ScriptError("output does not have`version` field") if not ( isinstance(version, int) and version >= 1 and version <= NoCP.LATEST_VERSION ): raise ScriptError("invalid API version: {}".format(version)) validator = _get_validator(version) ok = validator.validate(data) if not ok: raise ScriptError("validation error: {}", validator.errors) return data class ScriptError(base.PanelException): pass class NoCP(base.AbstractPanel): NAME = "no panel" exception = ScriptError @classmethod def is_installed(cls): return False async def enable_imunify_plugin(self, name=None): pass async def disable_imunify_plugin(self, plugin_name=None): pass @classmethod async def version(cls): return "0" async def get_user_domains(self): data = await _get_client_data() if data is None: return [] return [domain["name"] for domain in data["domains"]] async def get_users(self) -> List[str]: return [pw.pw_name for pw in get_non_system_users()] async def get_domain_to_owner(self) -> Dict[str, List[str]]: data = await _get_client_data() if data is None: return {} return { domain["name"]: [domain["owner"]] for domain in data["domains"] } async def get_domains_per_user(self) -> Dict[str, List[str]]: data = await _get_client_data() if data is None: return {} user_to_domains = defaultdict(list) # type: Dict[str, List[str]] for domain in data["domains"]: user_to_domains[domain["owner"]].append(domain["name"]) return dict(user_to_domains) def basedirs(self) -> Set[str]: return set() async def list_docroots(self) -> Dict[str, str]: # pragma: no cover return dict() async def panel_user_link(self, username) -> str: """ Returns panel url :return: str """ return "" @classmethod async def get_user_domains_details( cls, username: str ) -> list[base.DomainData]: return [] defence360agent/subsys/panels/plesk/0000755000000000000000000000000000000000000014476 5ustar defence360agent/subsys/panels/plesk/__init__.py0000644000000000000000000000005600000000000016610 0ustar from .panel import Plesk __all__ = ["Plesk"] defence360agent/subsys/panels/plesk/__pycache__/0000755000000000000000000000000000000000000016706 5ustar defence360agent/subsys/panels/plesk/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000043000000000000024103 0ustar r_j.ddlmZdgZdS))PleskrN)panelr__all__a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/__init__.pyr s" )rdefence360agent/subsys/panels/plesk/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000043000000000000023144 0ustar r_j.ddlmZdgZdS))PleskrN)panelr__all__a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/__init__.pyr s" )rdefence360agent/subsys/panels/plesk/__pycache__/api.cpython-311.opt-1.pyc0000644000000000000000000002707300000000000023131 0ustar r_j$dZddlZddlmZddlmZmZmZddlm Z m Z ddl m Z m Z mZmZejeZdZee de d ed efd Zd eeeeffd Zd eeeeffdZd eeeeefffdZd eefdZd eefdZd eeefdZd efdZd efdZd eefdZ dZ!e ddd ee fdZ"dS)z.Gather information from Plesk via DB querries.N) defaultdict)DictListSequence) DomainDataPanelException) CheckRunErrorasync_lru_cache check_runretry_oncKt|i|)N)r)argskwargss \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/api.pyraise_panel_exceptionrs $ )& ) ))) max_trieson_errorqueryreturnc^Ktdddd|gd{VS)Npleskdbz-Nz-e)r decoders r _run_queryrs=WdD$>?? ? ? ? ? ? ? G G I IIrcKtdd{V}tt}t |ddd|dddD]&\}}|dkr|||'|S)z'Return mapping: user -> user's domains.zselect login, name from domains left join hosting on dom_id = domains.id right join sys_users on hosting.sys_user_id = sys_users.idNrNULL)rsplitrlistzipappend)resultresult_mappinguserdomains rget_user_to_domainr*s L         egg  !&&NF14a4L&A,7700 f V   4 ' ' / / / rcKtdd{V}dt|ddd|dddDS)zReturn mapping: domain -> user.zselect name, login from domains left join hosting on dom_id = domains.id left join sys_users on hosting.sys_user_id = sys_users.idNci|] \}}||g Sr-).0r)r(s r z&get_domain_to_user..7s O O O|vtFTF O O Orrrr )rr"r$)r&s rget_domain_to_userr0,s~ K         egg  P Os6!$Q$<1/N/N O O OOrcKi}tdd{V}|dD]:}|s|d\}}}||ddd||<;|S)z{ Returns dict with user to email and locale pairs Not used, because MyImunify implemented for cPanel only yet z9SELECT CONCAT(login, ';',email, ';',locale) FROM clients;N ;-_)emaillocale)rr"replace) user_detailsresultsrecordr(r6r7s rget_user_detailsr<:s LCG--%%    $ll3//eVnnS#..  T rcTKtdd{VS)zReturn: list of domainszselect name from domainsNrr"r-rr get_domainsr?Qs5788 8 8 8 8 8 8 ? ? A AArcTKtdd{VS)z#Return: users that created by PleskzSELECT sys_users.login FROM sys_users JOIN hosting ON hosting.sys_user_id=sys_users.id JOIN domains ON hosting.dom_id=domains.id AND domains.webspace_id=0Nr>r-rr get_usersrAWsI C         egg rcrKtdd{V}d|dD}|S)a Returns [ ['admin', 'john.smith@tardis.gal', 'NULL', 'en-US', 'admin', 'NULL', 'NULL', 'NULL', '0'], ['user0', 'NULL', 'admin', 'en-US', 'client', '1', 'user0.com', '/var/www/vhosts/user0.com/httpdocs', '1'] ] There is only 1 return type. NULL is converted to 'NULL' Each possible empty string should be covered with IF(clients.email='', NULL, clients.email) or it will break data structure ar SELECT clients.login, IF(clients.email='', NULL, clients.email), parent.login, IF(clients.locale='', NULL, clients.locale), clients.type, domains.name, hosting.www_root, clients.status=16 suspended FROM clients LEFT JOIN clients parent ON parent.id=clients.parent_id LEFT JOIN domains ON domains.cl_id=clients.id LEFT JOIN hosting ON domains.id=hosting.dom_id; Nc:g|]}||Sr-r"r.strings r z*get_users_for_patchman..zs% J J J6 Jfllnn J J Jrr2r>)raw_datatupless rget_users_for_patchmanrJcsa          HK J8>>$+?+? J J JF MrcJKttdd{VS)z=Return: count active customers with at least one (any) domainz_select count(distinct cl_id) from clients c join domains d on d.cl_id = c.id where c.status = 0N)intrr-rr"count_customers_with_subscriptionsrM~sJ  0          rcnKtdd{V}d|dDS)Nz-SELECT email FROM clients WHERE type='admin';cg|]}||Sr-r-)r.r6s rrGz$get_admin_emails..s ; ; ;eU ;E ; ; ;rr2r>)emailss rget_admin_emailsrQsFMNN N N N N N NF ; ;v||D11 ; ; ;;rcXKd}t|d{VS)Nz.SELECT DISTINCT hosting.www_root FROM hosting;r>rs r list_docrootsrSs7 .s% F F Fv Ffllnn F F Frr2r>)sqldataretvals rlist_docroots_domains_usersrYsT D C D F F4::d+;+; F F FF Mrr <)maxsizettlcKd}t|d{V}d|dD}d|DS)Na SELECT 'domain' AS object_type, d.id AS object_id, d.name AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, CASE WHEN d.parentDomainId != 0 THEN 'subdomain' WHEN EXISTS ( SELECT 1 FROM `Subscriptions` s WHERE s.object_type = 'domain' AND s.object_id = d.id ) THEN 'primary' WHEN d.parentDomainId = 0 THEN 'addon' ELSE 'unknown' END AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domains d LEFT JOIN clients c ON d.cl_id = c.id LEFT JOIN hosting h ON d.id = h.dom_id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'subdomain' AS object_type, sd.id AS object_id, CONCAT(sd.name, '.', pd.name) AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, 'subdomain' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM subdomains sd JOIN domains pd ON sd.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'alias' AS object_type, da.id AS object_id, da.name AS domain_name, pd.name AS target_domain_name, c.id AS client_id, c.login AS client_login, 'alias' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domain_aliases da JOIN domains pd ON da.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id ORDER BY client_login, domain_type, domain_name; c:g|]}||Sr-rDrEs rrGz,get_user_domains_details..s%HHH6H HHHrr2c dg|]-}t|d|d|d|d.S)r)docrootr)typeusername)r)r.rows rrGz,get_user_domains_details..sI     3q6#a&s1vAOOO   rr>)rVrWrHs rget_user_domains_detailsrgsm= C|C DHHTZZ-=-=HHHH     r)#__doc__logging collectionsrtypingrrr"defence360agent.subsys.panels.baserrdefence360agent.utilsr r r r getLogger__name__loggerrstrrr*r0r<r?rAr#rJrLrMrQrSrYrgr-rrrrs44######''''''''''IIIIIIII  8 $ $*** -1/DEEEJCJCJJJFEJ$sDI~"6" P$sDI~"6 P P P PS$sCx.%8 9.B49BBBB c    d49o6#<<<<< -Xc]----   ###DZ(8DDD$#DDDrdefence360agent/subsys/panels/plesk/__pycache__/api.cpython-311.pyc0000644000000000000000000002707300000000000022172 0ustar r_j$dZddlZddlmZddlmZmZmZddlm Z m Z ddl m Z m Z mZmZejeZdZee de d ed efd Zd eeeeffd Zd eeeeffdZd eeeeefffdZd eefdZd eefdZd eeefdZd efdZd efdZd eefdZ dZ!e ddd ee fdZ"dS)z.Gather information from Plesk via DB querries.N) defaultdict)DictListSequence) DomainDataPanelException) CheckRunErrorasync_lru_cache check_runretry_oncKt|i|)N)r)argskwargss \/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/api.pyraise_panel_exceptionrs $ )& ) ))) max_trieson_errorqueryreturnc^Ktdddd|gd{VS)Npleskdbz-Nz-e)r decoders r _run_queryrs=WdD$>?? ? ? ? ? ? ? G G I IIrcKtdd{V}tt}t |ddd|dddD]&\}}|dkr|||'|S)z'Return mapping: user -> user's domains.zselect login, name from domains left join hosting on dom_id = domains.id right join sys_users on hosting.sys_user_id = sys_users.idNrNULL)rsplitrlistzipappend)resultresult_mappinguserdomains rget_user_to_domainr*s L         egg  !&&NF14a4L&A,7700 f V   4 ' ' / / / rcKtdd{V}dt|ddd|dddDS)zReturn mapping: domain -> user.zselect name, login from domains left join hosting on dom_id = domains.id left join sys_users on hosting.sys_user_id = sys_users.idNci|] \}}||g Sr-).0r)r(s r z&get_domain_to_user..7s O O O|vtFTF O O Orrrr )rr"r$)r&s rget_domain_to_userr0,s~ K         egg  P Os6!$Q$<1/N/N O O OOrcKi}tdd{V}|dD]:}|s|d\}}}||ddd||<;|S)z{ Returns dict with user to email and locale pairs Not used, because MyImunify implemented for cPanel only yet z9SELECT CONCAT(login, ';',email, ';',locale) FROM clients;N ;-_)emaillocale)rr"replace) user_detailsresultsrecordr(r6r7s rget_user_detailsr<:s LCG--%%    $ll3//eVnnS#..  T rcTKtdd{VS)zReturn: list of domainszselect name from domainsNrr"r-rr get_domainsr?Qs5788 8 8 8 8 8 8 ? ? A AArcTKtdd{VS)z#Return: users that created by PleskzSELECT sys_users.login FROM sys_users JOIN hosting ON hosting.sys_user_id=sys_users.id JOIN domains ON hosting.dom_id=domains.id AND domains.webspace_id=0Nr>r-rr get_usersrAWsI C         egg rcrKtdd{V}d|dD}|S)a Returns [ ['admin', 'john.smith@tardis.gal', 'NULL', 'en-US', 'admin', 'NULL', 'NULL', 'NULL', '0'], ['user0', 'NULL', 'admin', 'en-US', 'client', '1', 'user0.com', '/var/www/vhosts/user0.com/httpdocs', '1'] ] There is only 1 return type. NULL is converted to 'NULL' Each possible empty string should be covered with IF(clients.email='', NULL, clients.email) or it will break data structure ar SELECT clients.login, IF(clients.email='', NULL, clients.email), parent.login, IF(clients.locale='', NULL, clients.locale), clients.type, domains.name, hosting.www_root, clients.status=16 suspended FROM clients LEFT JOIN clients parent ON parent.id=clients.parent_id LEFT JOIN domains ON domains.cl_id=clients.id LEFT JOIN hosting ON domains.id=hosting.dom_id; Nc:g|]}||Sr-r"r.strings r z*get_users_for_patchman..zs% J J J6 Jfllnn J J Jrr2r>)raw_datatupless rget_users_for_patchmanrJcsa          HK J8>>$+?+? J J JF MrcJKttdd{VS)z=Return: count active customers with at least one (any) domainz_select count(distinct cl_id) from clients c join domains d on d.cl_id = c.id where c.status = 0N)intrr-rr"count_customers_with_subscriptionsrM~sJ  0          rcnKtdd{V}d|dDS)Nz-SELECT email FROM clients WHERE type='admin';cg|]}||Sr-r-)r.r6s rrGz$get_admin_emails..s ; ; ;eU ;E ; ; ;rr2r>)emailss rget_admin_emailsrQsFMNN N N N N N NF ; ;v||D11 ; ; ;;rcXKd}t|d{VS)Nz.SELECT DISTINCT hosting.www_root FROM hosting;r>rs r list_docrootsrSs7 .s% F F Fv Ffllnn F F Frr2r>)sqldataretvals rlist_docroots_domains_usersrYsT D C D F F4::d+;+; F F FF Mrr <)maxsizettlcKd}t|d{V}d|dD}d|DS)Na SELECT 'domain' AS object_type, d.id AS object_id, d.name AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, CASE WHEN d.parentDomainId != 0 THEN 'subdomain' WHEN EXISTS ( SELECT 1 FROM `Subscriptions` s WHERE s.object_type = 'domain' AND s.object_id = d.id ) THEN 'primary' WHEN d.parentDomainId = 0 THEN 'addon' ELSE 'unknown' END AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domains d LEFT JOIN clients c ON d.cl_id = c.id LEFT JOIN hosting h ON d.id = h.dom_id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'subdomain' AS object_type, sd.id AS object_id, CONCAT(sd.name, '.', pd.name) AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, 'subdomain' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM subdomains sd JOIN domains pd ON sd.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'alias' AS object_type, da.id AS object_id, da.name AS domain_name, pd.name AS target_domain_name, c.id AS client_id, c.login AS client_login, 'alias' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domain_aliases da JOIN domains pd ON da.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id ORDER BY client_login, domain_type, domain_name; c:g|]}||Sr-rDrEs rrGz,get_user_domains_details..s%HHH6H HHHrr2c dg|]-}t|d|d|d|d.S)r)docrootr)typeusername)r)r.rows rrGz,get_user_domains_details..sI     3q6#a&s1vAOOO   rr>)rVrWrHs rget_user_domains_detailsrgsm= C|C DHHTZZ-=-=HHHH     r)#__doc__logging collectionsrtypingrrr"defence360agent.subsys.panels.baserrdefence360agent.utilsr r r r getLogger__name__loggerrstrrr*r0r<r?rAr#rJrLrMrQrSrYrgr-rrrrs44######''''''''''IIIIIIII  8 $ $*** -1/DEEEJCJCJJJFEJ$sDI~"6" P$sDI~"6 P P P PS$sCx.%8 9.B49BBBB c    d49o6#<<<<< -Xc]----   ###DZ(8DDD$#DDDrdefence360agent/subsys/panels/plesk/__pycache__/panel.cpython-311.opt-1.pyc0000644000000000000000000003625600000000000023462 0ustar r_j$ddlZddlZddlZddlZddlmZddlmZddl m Z ddl m Z m Z mZmZmZddlmZddlmZddlmZdd lmZmZdd lmZd d lmZd dlmZd dlm Z dZ!dZ"ej#gdzZ$dZ%dZ&ej'e(Z)de*fdZ+dee*e*ffdZ,Gddej-Z.Gddej/Z0dS)N)Error) defaultdict)Path)DictListSetTupleUnion) ElementTreeis_plesk_installed)config) OsReleaseInfo check_run) get_hostname)base)api) PleskConfigz /etc/sw/keys/zext-imunify360)95399084438447zK/usr/local/psa/admin/plib/modules/imunify360/scripts/send-notifications.phpz@/opt/imunify360/venv/share/imunify360/scripts/send-notificationsreturncB||}||jSdS)z%Avoid AttributeError if tag not foundN)findtext)nodetag_nodes ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/panel.py_safe_get_textr$"s& IIcNNE z 2cd}d}|dD]J}t|ddkrt|d}t|ddkrt|d}K||fS)z.Return product name and filename from key datarvalue/struct/membernamefilenamez value/stringkey_product_name)findallr$)keyr)r*datas r# _get_key_datar.+sH 122DD $ ' ': 5 5%dN;;H $ ' '+= = =-dNCC  X %%r%ceZdZdS)PleskExceptionN)__name__ __module__ __qualname__r%r#r0r08sDr%r0c&eZdZdZgdezddgezdgdgdddZeZedZ e d Z e j d d Ze j d d Zd eefdZdZdZdZd eeeffdZdZd efdZedZd eefdZed efdZed e ed ffdZ!ed efdZ"dZ#d efdZ$ed ddZ%eded e&e j'fdZ(d S)!Plesk)1434658880z 49152-655351135224)inout)202153443)r>r?r@r:123)tcpudpctSNr clss r# is_installedzPlesk.is_installedJs!###r%cKtdd5}|dcdddS#1swxYwYdS)Nz/usr/local/psa/versionrr)openreadsplit)fs r#versionz Plesk.versionNs *C 0 0 'A6688>>##A& ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 's,A  AANc KdSrFr4)selfr(s r#enable_imunify_pluginzPlesk.enable_imunify_pluginS  r%c KdSrFr4)rR plugin_names r#disable_imunify_pluginzPlesk.disable_imunify_pluginWrTr%rcK tjd{VS#tj$r'}td|gcYd}~Sd}~wwxYw)z$Returns a list of Plesk system usersNzFailed to get users: %s)r get_usersrPanelExceptionloggererror)rRes r#rYzPlesk.get_users[so (((((( ("    LL2A 6 6 6IIIIII sAAAAc Ktjd{V}tt}td}|t jjt jjt jj d|D]\}}}}}} } } |dkrdn|||d<|dkrdn|||d<|||d<|dkrdn|||d<t||||d <tt| ||d <|| d  g||d <| dkr%||d  | | gd t|S) Nc$tjjSrF)r UserLevel REGULAR_USERr4r%r#z&Plesk.patchman_users..gs 4>3Nr%)adminresellerclientNULLremaillanguageusernameparentlevel suspendeddomains)domainpaths)rget_users_for_patchmanrdictupdaterr`ADMINRESSELERraintboolgetappendlistvalues) rRtuplesresclient_type_to_levelrirgrjlocale client_typernhomedirrls r#patchman_userszPlesk.patchman_userscs133333333$*+N+NOO##- N3.5               +0F??RRCM' ".4.>.>FCM* %(0CM* %,2f,<,>c ^tjtjt d}|dD]}t|ddkr|dD]}t|\}}|tkrttjtjt d|d}tj | ccSdS) zParse xml of registry and corresponding key file to retrive product key. return: str key or None if not found. z registry.xmlz struct/memberr(activer'keysz1{http://parallels.com/schemas/keys/aps/3}key-bodyN)r parseospathjoinPLESK_KEY_REGISTRYgetrootr+r$r.PLESK_IMUNIFY360_PRODUCT_NAMEbase64 b64decodeencodedecode)rHregistrymemberr,r*r) key_values r# _retrieve_keyzPlesk._retrieve_keys,$ GLL+^ < <  &&((00AA M MFff--99!>>*?@@ M MC1>s1C1C.$h'+HHH$2'- " $6!"!" ' %%  &/ 0@0@0B0BCCJJLLLLLLLItr%cK |}n6#tjttf$r}t d|zd}~wwxYw|rt d||St d)zkReturns registration key from registered keys, if possible, raise PleskException if not successful.z$failed to retrieve key with error %sNzkey retrieved %szThe key not found)rr ParseError base64ErrorFileNotFoundErrorr0r[info)rHresultr]s r# retrieve_keyzPlesk.retrieve_keys  M&&((FF& 5FG M M M !G!!KLL L M   KK*F 3 3 3M0111sA AA cPKtjd{V}d|DS)z1 :return: dict docroot to domain Nci|] \}}}|| Sr4r4).0docrootrnrs r# z'Plesk.list_docroots..s-   2GV   r%)rlist_docroots_domains_users)rRdocroot_domains_userss r# list_docrootszPlesk.list_docrootssJ'*&E&G&G G G G G G G  6K    r%c KdS)z8 Returns panel url :return: str rr4)rRris r#panel_user_linkzPlesk.panel_user_links rr%)usercKttsdStjjsdStj|sdS|||d}t|j d|tj |}ttg|d{V||dt!|du|dS) zB Notify a customer using Plesk Notifications Hook Fri) message_typeparamsrz .notify(%s))inputNr)rmainipbase_url host_server sent_to_rootr)rPLESK_NOTIFICATION_SCRIPT_PATHexistsr AdminContactsENABLE_ICONTACT_NOTIFICATIONSshould_send_user_notificationsr[rr1jsondumpsrPLESK_NOTIFICATION_HOOK_PATHr get_server_ipr)rHrrrr-stdins r#notifyz Plesk.notifys 233::<< 5#A 54dCCC 5 ,MM s|000$777 4  56ellnnMMMMMMMMMM)''))'>> DL    r%ricVKtjd{V}fd|DS)Nc*g|]}|jk |Sr4r)rrnris r# z2Plesk.get_user_domains_details..s,   80K0KF0K0K0Kr%)rget_user_domains_details)rHri all_domainss ` r#rzPlesk.get_user_domains_detailssT 8::::::::     !,    r%rF))r1r2r3NAMETCP_PORTS_PLESK OPEN_PORTSr0 exception classmethodrI staticmethodrPrensure_valid_panelrSrWrstrrYrrrrrrrurrrrrrr rrrrrry DomainDatarr4r%r#r6r6<s D877/I6?_4  ,++333   JI$$[$''\'T    T    c&"&"&"P''' ... -c3h---- ... >3>>>>66[6 /#c(////    [ eCI.[6 23 2 2 2[ 2   8<    [ 6  do    [   r%r6)1rrloggingrbinasciirr collectionsrpathlibrtypingrrrr r xml.etreer 3defence360agent.application.determine_hosting_panelr defence360agent.contractsrdefence360agent.utilsrrdefence360agent.utils.commonrrrrutilsrrrTCP_PORTS_COMMONrrr getLoggerr1r[rr$r.rZr0 AbstractPanelr6r4r%r#rs  ))))))######00000000000000!!!!!!-,,,,,::::::::555555$ 0'*H*H*HH!nF  8 $ $ &%S/ & & & &     T(   ` ` ` ` ` D ` ` ` ` ` r%defence360agent/subsys/panels/plesk/__pycache__/panel.cpython-311.pyc0000644000000000000000000003625600000000000022523 0ustar r_j$ddlZddlZddlZddlZddlmZddlmZddl m Z ddl m Z m Z mZmZmZddlmZddlmZddlmZdd lmZmZdd lmZd d lmZd dlmZd dlm Z dZ!dZ"ej#gdzZ$dZ%dZ&ej'e(Z)de*fdZ+dee*e*ffdZ,Gddej-Z.Gddej/Z0dS)N)Error) defaultdict)Path)DictListSetTupleUnion) ElementTreeis_plesk_installed)config) OsReleaseInfo check_run) get_hostname)base)api) PleskConfigz /etc/sw/keys/zext-imunify360)95399084438447zK/usr/local/psa/admin/plib/modules/imunify360/scripts/send-notifications.phpz@/opt/imunify360/venv/share/imunify360/scripts/send-notificationsreturncB||}||jSdS)z%Avoid AttributeError if tag not foundN)findtext)nodetag_nodes ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/panel.py_safe_get_textr$"s& IIcNNE z 2cd}d}|dD]J}t|ddkrt|d}t|ddkrt|d}K||fS)z.Return product name and filename from key datarvalue/struct/membernamefilenamez value/stringkey_product_name)findallr$)keyr)r*datas r# _get_key_datar.+sH 122DD $ ' ': 5 5%dN;;H $ ' '+= = =-dNCC  X %%r%ceZdZdS)PleskExceptionN)__name__ __module__ __qualname__r%r#r0r08sDr%r0c&eZdZdZgdezddgezdgdgdddZeZedZ e d Z e j d d Ze j d d Zd eefdZdZdZdZd eeeffdZdZd efdZedZd eefdZed efdZed e ed ffdZ!ed efdZ"dZ#d efdZ$ed ddZ%eded e&e j'fdZ(d S)!Plesk)1434658880z 49152-655351135224)inout)202153443)r>r?r@r:123)tcpudpctSNr clss r# is_installedzPlesk.is_installedJs!###r%cKtdd5}|dcdddS#1swxYwYdS)Nz/usr/local/psa/versionrr)openreadsplit)fs r#versionz Plesk.versionNs *C 0 0 'A6688>>##A& ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' 's,A  AANc KdSrFr4)selfr(s r#enable_imunify_pluginzPlesk.enable_imunify_pluginS  r%c KdSrFr4)rR plugin_names r#disable_imunify_pluginzPlesk.disable_imunify_pluginWrTr%rcK tjd{VS#tj$r'}td|gcYd}~Sd}~wwxYw)z$Returns a list of Plesk system usersNzFailed to get users: %s)r get_usersrPanelExceptionloggererror)rRes r#rYzPlesk.get_users[so (((((( ("    LL2A 6 6 6IIIIII sAAAAc Ktjd{V}tt}td}|t jjt jjt jj d|D]\}}}}}} } } |dkrdn|||d<|dkrdn|||d<|||d<|dkrdn|||d<t||||d <tt| ||d <|| d  g||d <| dkr%||d  | | gd t|S) Nc$tjjSrF)r UserLevel REGULAR_USERr4r%r#z&Plesk.patchman_users..gs 4>3Nr%)adminresellerclientNULLremaillanguageusernameparentlevel suspendeddomains)domainpaths)rget_users_for_patchmanrdictupdaterr`ADMINRESSELERraintboolgetappendlistvalues) rRtuplesresclient_type_to_levelrirgrjlocale client_typernhomedirrls r#patchman_userszPlesk.patchman_userscs133333333$*+N+NOO##- N3.5               +0F??RRCM' ".4.>.>FCM* %(0CM* %,2f,<,>c ^tjtjt d}|dD]}t|ddkr|dD]}t|\}}|tkrttjtjt d|d}tj | ccSdS) zParse xml of registry and corresponding key file to retrive product key. return: str key or None if not found. z registry.xmlz struct/memberr(activer'keysz1{http://parallels.com/schemas/keys/aps/3}key-bodyN)r parseospathjoinPLESK_KEY_REGISTRYgetrootr+r$r.PLESK_IMUNIFY360_PRODUCT_NAMEbase64 b64decodeencodedecode)rHregistrymemberr,r*r) key_values r# _retrieve_keyzPlesk._retrieve_keys,$ GLL+^ < <  &&((00AA M MFff--99!>>*?@@ M MC1>s1C1C.$h'+HHH$2'- " $6!"!" ' %%  &/ 0@0@0B0BCCJJLLLLLLLItr%cK |}n6#tjttf$r}t d|zd}~wwxYw|rt d||St d)zkReturns registration key from registered keys, if possible, raise PleskException if not successful.z$failed to retrieve key with error %sNzkey retrieved %szThe key not found)rr ParseError base64ErrorFileNotFoundErrorr0r[info)rHresultr]s r# retrieve_keyzPlesk.retrieve_keys  M&&((FF& 5FG M M M !G!!KLL L M   KK*F 3 3 3M0111sA AA cPKtjd{V}d|DS)z1 :return: dict docroot to domain Nci|] \}}}|| Sr4r4).0docrootrnrs r# z'Plesk.list_docroots..s-   2GV   r%)rlist_docroots_domains_users)rRdocroot_domains_userss r# list_docrootszPlesk.list_docrootssJ'*&E&G&G G G G G G G  6K    r%c KdS)z8 Returns panel url :return: str rr4)rRris r#panel_user_linkzPlesk.panel_user_links rr%)usercKttsdStjjsdStj|sdS|||d}t|j d|tj |}ttg|d{V||dt!|du|dS) zB Notify a customer using Plesk Notifications Hook Fri) message_typeparamsrz .notify(%s))inputNr)rmainipbase_url host_server sent_to_rootr)rPLESK_NOTIFICATION_SCRIPT_PATHexistsr AdminContactsENABLE_ICONTACT_NOTIFICATIONSshould_send_user_notificationsr[rr1jsondumpsrPLESK_NOTIFICATION_HOOK_PATHr get_server_ipr)rHrrrr-stdins r#notifyz Plesk.notifys 233::<< 5#A 54dCCC 5 ,MM s|000$777 4  56ellnnMMMMMMMMMM)''))'>> DL    r%ricVKtjd{V}fd|DS)Nc*g|]}|jk |Sr4r)rrnris r# z2Plesk.get_user_domains_details..s,   80K0KF0K0K0Kr%)rget_user_domains_details)rHri all_domainss ` r#rzPlesk.get_user_domains_detailssT 8::::::::     !,    r%rF))r1r2r3NAMETCP_PORTS_PLESK OPEN_PORTSr0 exception classmethodrI staticmethodrPrensure_valid_panelrSrWrstrrYrrrrrrrurrrrrrr rrrrrry DomainDatarr4r%r#r6r6<s D877/I6?_4  ,++333   JI$$[$''\'T    T    c&"&"&"P''' ... -c3h---- ... >3>>>>66[6 /#c(////    [ eCI.[6 23 2 2 2[ 2   8<    [ 6  do    [   r%r6)1rrloggingrbinasciirr collectionsrpathlibrtypingrrrr r xml.etreer 3defence360agent.application.determine_hosting_panelr defence360agent.contractsrdefence360agent.utilsrrdefence360agent.utils.commonrrrrutilsrrrTCP_PORTS_COMMONrrr getLoggerr1r[rr$r.rZr0 AbstractPanelr6r4r%r#rs  ))))))######00000000000000!!!!!!-,,,,,::::::::555555$ 0'*H*H*HH!nF  8 $ $ &%S/ & & & &     T(   ` ` ` ` ` D ` ` ` ` ` r%defence360agent/subsys/panels/plesk/__pycache__/upgrade_urls.cpython-311.opt-1.pyc0000644000000000000000000001055700000000000025053 0ustar r_j dZddlZddlZddlZddlmZddlmZddlm Z ej e Z edZ edZdZd Zd eefd Zd efd ZdS) a*Fetch Plesk buyUrl/upgradeLicenseUrl via extension context. Plesk resellers configure buyUrl through pm_Context, which is only accessible from within the Plesk extension runtime. This module dynamically creates a PHP script, runs it via ``plesk bin extension --exec``, and parses the JSON output. N)Path)Optional)is_plesk_installedz%/var/imunify360/plesk-ext-marketplacez4/usr/local/psa/admin/plib/modules/imunify360/scriptszget-upgrade-urls.phpz pm_Context::getBuyUrl(), 'upgradeLicenseUrl' => pm_Context::getUpgradeLicenseUrl(), ]); returncHttz } |t|dt jdddddtgdd }|jd krYt d |j|j d d  | dd S#t$rYd SwxYwtj|j | dS#t$rYSwxYw#tt jtjf$rN}t d|Yd }~ | dd S#t$rYd SwxYwd }~wwxYw# | dw#t$rYwwxYwxYw)zBCreate a temporary PHP script, execute it, and return parsed JSON.ipleskbin extensionz--exec imunify360T)capture_outputtimeoutrz)plesk extension --exec failed (rc=%d): %sNi) missing_okz&Failed to fetch Plesk upgrade URLs: %s)PLESK_EXT_SCRIPTS_DIR _SCRIPT_NAME write_text_SCRIPT_CONTENTchmod subprocessrun returncodeloggerwarningstderrunlinkOSErrorjsonloadsstdoutTimeoutExpiredJSONDecodeError) script_pathresultexcs e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/upgrade_urls.py_run_plesk_scriptr&#s',6K!/// %             ! ! NN;! dsd#         $  / / / / /    DD z&-((     $  / / / /    D  Z.0D E?EEEttt    $  / / / / /    DD      $  / / / /    D sB DB55 CCDC66 DD!E5'E0E8E E-,E-0E55E88F!:FF! FF!FF!c(ddd}trts|St}|s|S|dpd|dpddS)a%Return Plesk buyUrl and upgradeLicenseUrl, or empty strings. Only runs when Plesk is installed and the extension was installed from the Plesk marketplace. Returns: dict with keys ``buy_url`` and ``upgrade_license_url``, both empty strings when not available. )buy_urlupgrade_license_urlbuyUrlupgradeLicenseUrl)rPLESK_MARKETPLACE_FLAGexistsr&getstrip)emptydatas r%get_plesk_upgrade_urlsr3Js2 6 6E   '='D'D'F'F   D  HHX&&,"3355 $)< = = CJJLL  )__doc__rloggingrpathlibrtypingr3defence360agent.application.determine_hosting_panelr getLogger__name__rr-rrrdictr&r3r4r%r>s   8 $ $EFF:& $8D>$$$$Nr4defence360agent/subsys/panels/plesk/__pycache__/upgrade_urls.cpython-311.pyc0000644000000000000000000001055700000000000024114 0ustar r_j dZddlZddlZddlZddlmZddlmZddlm Z ej e Z edZ edZdZd Zd eefd Zd efd ZdS) a*Fetch Plesk buyUrl/upgradeLicenseUrl via extension context. Plesk resellers configure buyUrl through pm_Context, which is only accessible from within the Plesk extension runtime. This module dynamically creates a PHP script, runs it via ``plesk bin extension --exec``, and parses the JSON output. N)Path)Optional)is_plesk_installedz%/var/imunify360/plesk-ext-marketplacez4/usr/local/psa/admin/plib/modules/imunify360/scriptszget-upgrade-urls.phpz pm_Context::getBuyUrl(), 'upgradeLicenseUrl' => pm_Context::getUpgradeLicenseUrl(), ]); returncHttz } |t|dt jdddddtgdd }|jd krYt d |j|j d d  | dd S#t$rYd SwxYwtj|j | dS#t$rYSwxYw#tt jtjf$rN}t d|Yd }~ | dd S#t$rYd SwxYwd }~wwxYw# | dw#t$rYwwxYwxYw)zBCreate a temporary PHP script, execute it, and return parsed JSON.ipleskbin extensionz--exec imunify360T)capture_outputtimeoutrz)plesk extension --exec failed (rc=%d): %sNi) missing_okz&Failed to fetch Plesk upgrade URLs: %s)PLESK_EXT_SCRIPTS_DIR _SCRIPT_NAME write_text_SCRIPT_CONTENTchmod subprocessrun returncodeloggerwarningstderrunlinkOSErrorjsonloadsstdoutTimeoutExpiredJSONDecodeError) script_pathresultexcs e/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/upgrade_urls.py_run_plesk_scriptr&#s',6K!/// %             ! ! NN;! dsd#         $  / / / / /    DD z&-((     $  / / / /    D  Z.0D E?EEEttt    $  / / / / /    DD      $  / / / /    D sB DB55 CCDC66 DD!E5'E0E8E E-,E-0E55E88F!:FF! FF!FF!c(ddd}trts|St}|s|S|dpd|dpddS)a%Return Plesk buyUrl and upgradeLicenseUrl, or empty strings. Only runs when Plesk is installed and the extension was installed from the Plesk marketplace. Returns: dict with keys ``buy_url`` and ``upgrade_license_url``, both empty strings when not available. )buy_urlupgrade_license_urlbuyUrlupgradeLicenseUrl)rPLESK_MARKETPLACE_FLAGexistsr&getstrip)emptydatas r%get_plesk_upgrade_urlsr3Js2 6 6E   '='D'D'F'F   D  HHX&&,"3355 $)< = = CJJLL  )__doc__rloggingrpathlibrtypingr3defence360agent.application.determine_hosting_panelr getLogger__name__rr-rrrdictr&r3r4r%r>s   8 $ $EFF:& $8D>$$$$Nr4defence360agent/subsys/panels/plesk/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000000116600000000000023513 0ustar r_j.ddlmZGddeZdS))KWConfigceZdZdZdZdZdS) PleskConfigz ^{}\s+(.*)?$z{} {}z/etc/psa/psa.confN)__name__ __module__ __qualname__SEARCH_PATTERN WRITE_PATTERNDEFAULT_FILENAME^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/utils.pyrrs $NM*r rN)defence360agent.utils.kwconfigrrr r rrsK333333+++++(+++++r defence360agent/subsys/panels/plesk/__pycache__/utils.cpython-311.pyc0000644000000000000000000000116600000000000022554 0ustar r_j.ddlmZGddeZdS))KWConfigceZdZdZdZdZdS) PleskConfigz ^{}\s+(.*)?$z{} {}z/etc/psa/psa.confN)__name__ __module__ __qualname__SEARCH_PATTERN WRITE_PATTERNDEFAULT_FILENAME^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/subsys/panels/plesk/utils.pyrrs $NM*r rN)defence360agent.utils.kwconfigrrr r rrsK333333+++++(+++++r defence360agent/subsys/panels/plesk/api.py0000644000000000000000000001644400000000000015632 0ustar """Gather information from Plesk via DB querries.""" import logging from collections import defaultdict from typing import Dict, List, Sequence from defence360agent.subsys.panels.base import DomainData, PanelException from defence360agent.utils import ( CheckRunError, async_lru_cache, check_run, retry_on, ) logger = logging.getLogger(__name__) async def raise_panel_exception(*args, **kwargs): raise PanelException(*args, **kwargs) @retry_on(CheckRunError, max_tries=3, on_error=raise_panel_exception) async def _run_query(query: str) -> str: return (await check_run(["plesk", "db", "-N", "-e", query])).decode() async def get_user_to_domain() -> Dict[str, List[str]]: """Return mapping: user -> user's domains.""" result = ( await _run_query( "select login, name from domains " " left join hosting on dom_id = domains.id " " right join sys_users on hosting.sys_user_id = sys_users.id" ) ).split() result_mapping = defaultdict(list) for user, domain in zip(result[0::2], result[1::2]): if domain != "NULL": result_mapping[user].append(domain) return result_mapping async def get_domain_to_user() -> Dict[str, List[str]]: """Return mapping: domain -> user.""" result = ( await _run_query( "select name, login " "from domains " " left join hosting on dom_id = domains.id " " left join sys_users on hosting.sys_user_id = sys_users.id" ) ).split() return {domain: [user] for domain, user in zip(result[0::2], result[1::2])} async def get_user_details() -> Dict[str, Dict[str, str]]: """ Returns dict with user to email and locale pairs Not used, because MyImunify implemented for cPanel only yet """ user_details = {} results = await _run_query( "SELECT CONCAT(login, ';',email, ';',locale) FROM clients;" ) for record in results.split("\n"): if not record: continue user, email, locale = record.split(";") user_details[user] = { "email": email, "locale": locale.replace("-", "_"), } return user_details async def get_domains() -> List[str]: """Return: list of domains""" return (await _run_query("select name from domains")).split() async def get_users() -> List[str]: """Return: users that created by Plesk""" return ( await _run_query( "SELECT sys_users.login FROM sys_users JOIN hosting ON" " hosting.sys_user_id=sys_users.id JOIN domains ON" " hosting.dom_id=domains.id AND domains.webspace_id=0" ) ).split() async def get_users_for_patchman() -> list[list[str]]: """ Returns [ ['admin', 'john.smith@tardis.gal', 'NULL', 'en-US', 'admin', 'NULL', 'NULL', 'NULL', '0'], ['user0', 'NULL', 'admin', 'en-US', 'client', '1', 'user0.com', '/var/www/vhosts/user0.com/httpdocs', '1'] ] There is only 1 return type. NULL is converted to 'NULL' Each possible empty string should be covered with IF(clients.email='', NULL, clients.email) or it will break data structure """ raw_data = await _run_query( """ SELECT clients.login, IF(clients.email='', NULL, clients.email), parent.login, IF(clients.locale='', NULL, clients.locale), clients.type, domains.name, hosting.www_root, clients.status=16 suspended FROM clients LEFT JOIN clients parent ON parent.id=clients.parent_id LEFT JOIN domains ON domains.cl_id=clients.id LEFT JOIN hosting ON domains.id=hosting.dom_id; """ ) tuples = [string.split() for string in raw_data.split("\n") if string] return tuples async def count_customers_with_subscriptions() -> int: # pragma: no cover """Return: count active customers with at least one (any) domain""" return int( await _run_query( "select count(distinct cl_id) from clients c join domains d on " "d.cl_id = c.id where c.status = 0" ) ) async def get_admin_emails() -> list: emails = await _run_query("SELECT email FROM clients WHERE type='admin';") return [email for email in emails.split("\n") if email] async def list_docroots() -> Sequence[str]: query = "SELECT DISTINCT hosting.www_root FROM hosting;" return (await _run_query(query)).split() async def list_docroots_domains_users(): sql = ( "select hosting.www_root, domains.name, sys_users.login" " from hosting" " inner join domains on hosting.dom_id = domains.id" " inner join sys_users on hosting.sys_user_id=sys_users.id" ) data = await _run_query(sql) retval = [string.split() for string in data.split("\n") if string] return retval @async_lru_cache(maxsize=1, ttl=60) async def get_user_domains_details() -> list[DomainData]: sql = """ SELECT 'domain' AS object_type, d.id AS object_id, d.name AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, CASE WHEN d.parentDomainId != 0 THEN 'subdomain' WHEN EXISTS ( SELECT 1 FROM `Subscriptions` s WHERE s.object_type = 'domain' AND s.object_id = d.id ) THEN 'primary' WHEN d.parentDomainId = 0 THEN 'addon' ELSE 'unknown' END AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domains d LEFT JOIN clients c ON d.cl_id = c.id LEFT JOIN hosting h ON d.id = h.dom_id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'subdomain' AS object_type, sd.id AS object_id, CONCAT(sd.name, '.', pd.name) AS domain_name, NULL AS target_domain_name, c.id AS client_id, c.login AS client_login, 'subdomain' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM subdomains sd JOIN domains pd ON sd.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id UNION ALL SELECT 'alias' AS object_type, da.id AS object_id, da.name AS domain_name, pd.name AS target_domain_name, c.id AS client_id, c.login AS client_login, 'alias' AS domain_type, h.www_root AS docroot, su.login AS sys_user_login FROM domain_aliases da JOIN domains pd ON da.dom_id = pd.id LEFT JOIN hosting h ON pd.id = h.dom_id LEFT JOIN clients c ON pd.cl_id = c.id LEFT JOIN sys_users su ON h.sys_user_id = su.id ORDER BY client_login, domain_type, domain_name; """ data = await _run_query(sql) raw_data = [string.split() for string in data.split("\n") if string] return [ DomainData(docroot=row[7], domain=row[2], type=row[6], username=row[8]) for row in raw_data ] defence360agent/subsys/panels/plesk/panel.py0000644000000000000000000002201000000000000016142 0ustar import base64 import json import logging import os from binascii import Error as base64Error from collections import defaultdict from pathlib import Path from typing import Dict, List, Set, Tuple, Union from xml.etree import ElementTree from defence360agent.application.determine_hosting_panel import ( is_plesk_installed, ) from defence360agent.contracts import config from defence360agent.utils import OsReleaseInfo, check_run from defence360agent.utils.common import get_hostname from .. import base from . import api from .utils import PleskConfig PLESK_KEY_REGISTRY = "/etc/sw/keys/" PLESK_IMUNIFY360_PRODUCT_NAME = "ext-imunify360" TCP_PORTS_PLESK = base.TCP_PORTS_COMMON + ["953", "990", "8443", "8447"] PLESK_NOTIFICATION_SCRIPT_PATH = "/usr/local/psa/admin/plib/modules/imunify360/scripts/send-notifications.php" PLESK_NOTIFICATION_HOOK_PATH = ( "/opt/imunify360/venv/share/imunify360/scripts/send-notifications" ) logger = logging.getLogger(__name__) def _safe_get_text(node, tag) -> str: """Avoid AttributeError if tag not found""" _node = node.find(tag) if _node is not None: return _node.text return "" def _get_key_data(key) -> Tuple[str, str]: """Return product name and filename from key data""" filename = "" key_product_name = "" for data in key.findall("value/struct/member"): if _safe_get_text(data, "name") == "filename": filename = _safe_get_text(data, "value/string") if _safe_get_text(data, "name") == "key_product_name": key_product_name = _safe_get_text(data, "value/string") return key_product_name, filename class PleskException(base.PanelException): pass class Plesk(base.AbstractPanel): NAME = "Plesk" OPEN_PORTS = { "tcp": { "in": ["143", "465", "8880", "49152-65535"] + TCP_PORTS_PLESK, "out": ["113", "5224"] + TCP_PORTS_PLESK, }, "udp": { "in": ["20", "21", "53", "443"], "out": ["20", "21", "53", "113", "123"], }, } exception = PleskException @classmethod def is_installed(cls): return is_plesk_installed() @staticmethod async def version(): with open("/usr/local/psa/version", "r") as f: return f.read().split()[0] @base.ensure_valid_panel() async def enable_imunify_plugin(self, name=None): pass @base.ensure_valid_panel() async def disable_imunify_plugin(self, plugin_name=None): pass async def get_users(self) -> List[str]: """Returns a list of Plesk system users""" try: return await api.get_users() except base.PanelException as e: logger.error("Failed to get users: %s", e) return [] async def patchman_users(self): tuples = await api.get_users_for_patchman() res = defaultdict(dict) # https://cloudlinux.slite.com/app/docs/nrQKL-Raf_3ps4#e0bf3d51 client_type_to_level = defaultdict(lambda: base.UserLevel.REGULAR_USER) client_type_to_level.update( { "admin": base.UserLevel.ADMIN, "reseller": base.UserLevel.RESSELER, "client": base.UserLevel.REGULAR_USER, } ) for ( username, email, parent, locale, client_type, domain, homedir, suspended, ) in tuples: res[username]["email"] = "" if email == "NULL" else email res[username]["language"] = "" if locale == "NULL" else locale res[username]["username"] = username res[username]["parent"] = "" if parent == "NULL" else parent res[username]["level"] = int(client_type_to_level[client_type]) res[username]["suspended"] = bool(int(suspended)) if res[username].get("domains") is None: res[username]["domains"] = [] if domain != "NULL": res[username]["domains"].append( { "domain": domain, "paths": [homedir], } ) return list(res.values()) async def get_user_domains(self): """ :return: list: domains hosted on server via plesk """ return await api.get_domains() async def get_domain_to_owner(self): """ :return: domain to list of users pairs """ return await api.get_domain_to_user() async def get_user_to_email(self) -> Dict[str, str]: """ Returns dict with user to email pairs """ return await api.get_user_to_email() async def get_domains_per_user(self): """ :return: user to list of domains pairs """ return await api.get_user_to_domain() async def users_count(self) -> int: return await api.count_customers_with_subscriptions() @classmethod def get_modsec_config_path(cls): if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: return "/etc/apache2/mods-available/security2.conf" else: return "/etc/httpd/conf.d/security2.conf" def basedirs(self) -> Set[str]: basedir = PleskConfig("HTTPD_VHOSTS_D").get() return {basedir} if basedir else set() @classmethod def base_home_dir(cls, _) -> Path: # Local import to save memory on other panels from configparser import ConfigParser with open("/etc/psa/psa.conf") as c: text = "[dummy section]\n" + c.read() config = ConfigParser(delimiters=[" ", "\t"]) config.read_string(text) base_dir = Path( config["dummy section"].get("HTTPD_VHOSTS_D", "/var/www/vhosts") ) return base_dir @classmethod def _retrieve_key(cls) -> Union[str, None]: """Parse xml of registry and corresponding key file to retrive product key. return: str key or None if not found. """ registry = ElementTree.parse( os.path.join(PLESK_KEY_REGISTRY, "registry.xml") ) for member in registry.getroot().findall("struct/member"): if _safe_get_text(member, "name") == "active": for key in member.findall("value/struct/member"): key_product_name, filename = _get_key_data(key) if key_product_name == PLESK_IMUNIFY360_PRODUCT_NAME: key_value = _safe_get_text( ElementTree.parse( os.path.join( PLESK_KEY_REGISTRY, "keys", filename ) ), "{http://parallels.com/schemas/keys/aps/3}" "key-body", ) return base64.b64decode(key_value.encode()).decode() return None @classmethod async def retrieve_key(cls) -> str: """Returns registration key from registered keys, if possible, raise PleskException if not successful.""" try: result = cls._retrieve_key() except (ElementTree.ParseError, base64Error, FileNotFoundError) as e: raise PleskException("failed to retrieve key with error %s" % e) if result: logger.info("key retrieved %s", result) return result raise PleskException("The key not found") async def list_docroots(self): """ :return: dict docroot to domain """ docroot_domains_users = await api.list_docroots_domains_users() return { docroot: domain for docroot, domain, _ in docroot_domains_users } async def panel_user_link(self, username) -> str: """ Returns panel url :return: str """ return "" @classmethod async def notify(cls, *, message_type, params, user=None): """ Notify a customer using Plesk Notifications Hook """ if not Path(PLESK_NOTIFICATION_SCRIPT_PATH).exists(): return False if not config.AdminContacts.ENABLE_ICONTACT_NOTIFICATIONS: return False if not config.should_send_user_notifications(username=user): return False data = {"message_type": message_type, "params": params, "user": user} logger.info(f"{cls.__name__}.notify(%s)", data) stdin = json.dumps(data) await check_run([PLESK_NOTIFICATION_HOOK_PATH], input=stdin.encode()) return { "message_type": message_type, "mainip": cls.get_server_ip(), "base_url": "", "host_server": get_hostname(), "sent_to_root": user is None, "params": params, } @classmethod async def get_user_domains_details( cls, username: str ) -> list[base.DomainData]: all_domains = await api.get_user_domains_details() return [ domain for domain in all_domains if domain.username == username ] defence360agent/subsys/panels/plesk/upgrade_urls.py0000644000000000000000000000557700000000000017562 0ustar """Fetch Plesk buyUrl/upgradeLicenseUrl via extension context. Plesk resellers configure buyUrl through pm_Context, which is only accessible from within the Plesk extension runtime. This module dynamically creates a PHP script, runs it via ``plesk bin extension --exec``, and parses the JSON output. """ import json import logging import subprocess from pathlib import Path from typing import Optional from defence360agent.application.determine_hosting_panel import ( is_plesk_installed, ) logger = logging.getLogger(__name__) PLESK_MARKETPLACE_FLAG = Path("/var/imunify360/plesk-ext-marketplace") PLESK_EXT_SCRIPTS_DIR = Path( "/usr/local/psa/admin/plib/modules/imunify360/scripts" ) _SCRIPT_NAME = "get-upgrade-urls.php" _SCRIPT_CONTENT = """\ pm_Context::getBuyUrl(), 'upgradeLicenseUrl' => pm_Context::getUpgradeLicenseUrl(), ]); """ def _run_plesk_script() -> Optional[dict]: """Create a temporary PHP script, execute it, and return parsed JSON.""" script_path = PLESK_EXT_SCRIPTS_DIR / _SCRIPT_NAME try: script_path.write_text(_SCRIPT_CONTENT) # The daemon runs with umask 0o007, so write_text creates the file # as 0o660 — unreadable by the non-root account Plesk uses to run # ``plesk bin extension --exec``. Force world-readable. script_path.chmod(0o644) result = subprocess.run( [ "plesk", "bin", "extension", "--exec", "imunify360", _SCRIPT_NAME, ], capture_output=True, timeout=30, ) if result.returncode != 0: logger.warning( "plesk extension --exec failed (rc=%d): %s", result.returncode, result.stderr[:500], ) return None return json.loads(result.stdout) except (OSError, subprocess.TimeoutExpired, json.JSONDecodeError) as exc: logger.warning("Failed to fetch Plesk upgrade URLs: %s", exc) return None finally: try: script_path.unlink(missing_ok=True) except OSError: pass def get_plesk_upgrade_urls() -> dict: """Return Plesk buyUrl and upgradeLicenseUrl, or empty strings. Only runs when Plesk is installed and the extension was installed from the Plesk marketplace. Returns: dict with keys ``buy_url`` and ``upgrade_license_url``, both empty strings when not available. """ empty = {"buy_url": "", "upgrade_license_url": ""} if not is_plesk_installed() or not PLESK_MARKETPLACE_FLAG.exists(): return empty data = _run_plesk_script() if not data: return empty return { "buy_url": (data.get("buyUrl") or "").strip(), "upgrade_license_url": (data.get("upgradeLicenseUrl") or "").strip(), } defence360agent/subsys/panels/plesk/utils.py0000644000000000000000000000027700000000000016216 0ustar from defence360agent.utils.kwconfig import KWConfig class PleskConfig(KWConfig): SEARCH_PATTERN = r"^{}\s+(.*)?$" WRITE_PATTERN = "{} {}" DEFAULT_FILENAME = "/etc/psa/psa.conf" defence360agent/subsys/persistent_state.py0000644000000000000000000000360500000000000016054 0ustar import json from logging import getLogger from pathlib import Path from typing import Literal from defence360agent.contracts.config import ANTIVIRUS_MODE from defence360agent.contracts.plugins import Scope logger = getLogger(__name__) BASE_DIR = Path("/var/imunify360") PERSISTENT_STATE_DIR = BASE_DIR / ".persistent_state" LOCK_FILES = set() def register_lock_file( lock_file: str, scope: Literal[Scope.AV, Scope.IM360, Scope.AV_IM360] ) -> Path: """Register lock file for further usage.""" _lock_file = PERSISTENT_STATE_DIR / f".{lock_file}.lock" if scope == Scope.AV_IM360: LOCK_FILES.add(_lock_file) elif scope == Scope.AV and ANTIVIRUS_MODE: LOCK_FILES.add(_lock_file) elif scope == Scope.IM360 and not ANTIVIRUS_MODE: LOCK_FILES.add(_lock_file) return _lock_file def save_state(class_name: str, values: dict): """Save state to a file in .persistent_state folder.""" folder_path = PERSISTENT_STATE_DIR try: folder_path.mkdir(parents=True, exist_ok=True) file_path = folder_path / f"{class_name}.state" json.dump(values, file_path.open("w")) except (AttributeError, OSError) as e: logger.error("Failed to save state: %s %s", class_name, e) def load_state(class_name) -> dict: """Load state from a file in .persistent_state folder.""" folder_path = PERSISTENT_STATE_DIR file_path = folder_path / f"{class_name}.state" if file_path.exists(): try: return json.load(file_path.open("r")) except (json.JSONDecodeError, OSError, UnicodeDecodeError) as e: logger.error("Failed to load state: %s %s", class_name, e) return dict() def remove_unused_locks(): """Remove all unused lock files from .persistent_state folder.""" for lock_file in PERSISTENT_STATE_DIR.glob("*.lock"): if lock_file not in LOCK_FILES: lock_file.unlink() defence360agent/subsys/svcctl.py0000644000000000000000000001340600000000000013752 0ustar import asyncio import logging import os import subprocess as su from typing import Iterable from defence360agent.contracts.config import Core from defence360agent.utils import check_run, CheckRunError, run, OsReleaseInfo logger = logging.getLogger(__name__) DOS_PROTECTOR_SERVICE_NAME = "imunify360-dos-protection" UAL_SERVICE_NAME = "imunify360-unified-access-logger" PAM_SERVICE_NAME = "imunify360-pam" AUDITD_SERVICE_NAME = "imunify-auditd-log-reader" SCANLOGD_SERVICE_NAME = "imunify360-scanlogd" AGENT_SERVICE_NAME = "imunify360-agent" def _apply_cmd(func): async def wrapper(*args, **kwargs): cmd = func(*args, **kwargs) logger.debug("check_call(%r)", cmd) await check_run(cmd) return wrapper async def _reset_failed_state( services: Iterable["_SystemctlBased"], ): for s in services: try: await s.reset_failed() await s.restart() except CheckRunError as e: logger.warning( "Failed to reset failed state for service %s: %s", s, e ) return for _ in range(10): if await s.is_active(): break logger.warning( "Service %s is still not active, sleep for %s seconds", s, 1 ) await asyncio.sleep(1) class _SystemctlBased: SVC_CTL_BIN = "systemctl" def __init__(self, service_name): self._service_name = service_name @_apply_cmd def start(self): return [self.SVC_CTL_BIN, "start", self._service_name] @_apply_cmd def stop(self): return [self.SVC_CTL_BIN, "stop", self._service_name] @_apply_cmd def restart(self): return [self.SVC_CTL_BIN, "restart", self._service_name] @_apply_cmd def _enable_now(self, *, now: bool): return [ self.SVC_CTL_BIN, "enable", *(["--now"] if now else []), self._service_name, ] async def enable(self, *, now: bool): await self._enable_now(now=now) # WARN: Ubuntu 16.04 demonstrates very special behavior of the # `systemcl enable --now` command - if the unit is stopped it # wouldn't be started. We need to handle that case. # TODO: Remove this case on dropping support for Ubuntu 16.04. osinfo = {} try: OsReleaseInfo.dict_from_file(osinfo) except (FileNotFoundError, PermissionError): return if osinfo.get("ID", "").lower() != "ubuntu": return if osinfo.get("VERSION_ID", "") == "16.04": await self.restart() async def is_enabled(self): cmd = [self.SVC_CTL_BIN, "is-enabled", self._service_name] proc = await asyncio.create_subprocess_exec( *cmd, stdout=su.DEVNULL, stderr=su.DEVNULL ) await proc.communicate() rc = await proc.wait() return rc == 0 def is_enabled_sync(self): cmd = [self.SVC_CTL_BIN, "is-enabled", self._service_name] rc = su.call(cmd, stdout=su.DEVNULL, stderr=su.DEVNULL) return rc == 0 @_apply_cmd def disable(self, *, now: bool): return [ self.SVC_CTL_BIN, "disable", *(["--now"] if now else []), self._service_name, ] @_apply_cmd def reload(self): return [self.SVC_CTL_BIN, "reload", self._service_name] async def is_active(self): cmd = [self.SVC_CTL_BIN, "is-active", self._service_name] exit_code, _, _ = await run(cmd) return exit_code == 0 @_apply_cmd def reset_failed(self): return [self.SVC_CTL_BIN, "reset-failed", self._service_name] def unit_exists(self): cp = su.run( [self.SVC_CTL_BIN, "cat", self._service_name], stdout=su.DEVNULL, stderr=su.DEVNULL, ) return cp.returncode == 0 class _CentOs7(_SystemctlBased): SVC_CTL_BIN = "/usr/bin/systemctl" class _DebianUbuntu(_SystemctlBased): SVC_CTL_BIN = "/bin/systemctl" def adaptor(service_name): for a in (_DebianUbuntu, _CentOs7): if os.path.exists(a.SVC_CTL_BIN): return a(service_name) raise RuntimeError("Cannot instantiate appropriate adaptor.") async def activate_socket_service(service_name): agent_service = adaptor(service_name) agent_service_socket = adaptor(f"{service_name}.socket") if ( await agent_service_socket.is_enabled() and not await agent_service_socket.is_active() ): # reset the main service, which will trigger socket activation await agent_service_socket.reset_failed() await _reset_failed_state((agent_service,)) # wait some times until socket activates for _ in range(5): await asyncio.sleep(1) if await agent_service_socket.is_active(): return logger.error( f"Failed to await active {service_name}.socket after reseting" f" {service_name}" ) def imunify360_service(): return adaptor(Core.SVC_NAME) def imunify360_dos_protector_service(): try: return adaptor(DOS_PROTECTOR_SERVICE_NAME) except RuntimeError: logger.info("DOS Protector service is not available on this system") return None def imunify360_ual_service(): return adaptor(UAL_SERVICE_NAME) def imunify360_pam_service(): return adaptor(PAM_SERVICE_NAME) def imunify360_scanlogd_service(): return adaptor(SCANLOGD_SERVICE_NAME) def imunify360_agent_service(): return adaptor(AGENT_SERVICE_NAME) def imunify360_auditd_service(): unit = adaptor(AUDITD_SERVICE_NAME) if unit.unit_exists(): return adaptor(AUDITD_SERVICE_NAME) logger.info("Auditd-log-reader service is not available on this system") return None defence360agent/subsys/sysctl.py0000644000000000000000000000057300000000000013776 0ustar import os def _build_path(name): return os.path.join(os.sep, "proc", "sys", *name.split(".")) def read(name): with open(_build_path(name)) as f: data = f.read().strip() if data.isdigit: return int(data) else: return data def write(name, value): with open(_build_path(name), "w") as f: f.write(str(value)) defence360agent/subsys/systemd_notifier.py0000644000000000000000000000336000000000000016041 0ustar """Notify systemd about process state""" import logging import os import socket from defence360agent.contracts.config import ANTIVIRUS_MODE logger = logging.getLogger(__name__) _notify_socket_addr = None _socket_detached = False class AgentState(object): """Allowed agent state for notifying systemd.""" READY = "READY=1" STARTING = "STATUS=Starting main process" MIGRATING = "STATUS=Applying database migrations" DAEMONIZED = "STATUS=Demonized" def _take_notify_socket(): # Capture $NOTIFY_SOCKET once and drop it from the environment, so child # processes (systemctl and other libsystemd-aware tools) do not inherit it # and emit sd_notify datagrams systemd cannot attribute to this unit. global _notify_socket_addr, _socket_detached if not _socket_detached: _notify_socket_addr = os.environ.pop("NOTIFY_SOCKET", None) _socket_detached = True return _notify_socket_addr def notify(state): """ Send notification to systemd, allowed formats described here https://www.freedesktop.org/software/systemd/man/sd_notify.html For example: notify("STATUS=Almost ready") """ if ANTIVIRUS_MODE: return addr = _take_notify_socket() if not addr: return # systemd uses the abstract socket namespace when the path begins with '@'. connect_addr = "\0" + addr[1:] if addr.startswith("@") else addr try: with socket.socket( socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC ) as sock: sock.connect(connect_addr) sock.sendall(state.encode()) except OSError as e: logger.exception( "some problem has occurred during notifying of systemd: %s", e, ) defence360agent/subsys/web_server.py0000644000000000000000000006573500000000000014633 0ustar import asyncio import functools import inspect import io import logging import os import re import shlex import shutil import string import xml.etree.ElementTree as ET from contextlib import suppress from contextvars import ContextVar from datetime import timedelta from packaging.version import Version from pathlib import Path from subprocess import CalledProcessError, check_call, check_output, DEVNULL from typing import Any, Callable, List, Optional, Set, Tuple, Iterable import psutil from defence360agent.api.integration_conf import IntegrationConfig from defence360agent.application.determine_hosting_panel import ( is_generic_panel_installed, is_plesk_installed, ) from defence360agent.internals.global_scope import g from defence360agent.utils import ( async_lru_cache, atomic_rewrite, check_run, get_system_user_names, OsReleaseInfo, CheckRunError, TimedCache, BACKUP_EXTENSION, ) from defence360agent.utils.common import webserver_gracefull_restart GRACEFUL_RESTART_MIN_PERIOD = int( os.environ.get("IM360_GRACEFUL_RESTART_MIN_PERIOD", 5 * 60) ) # seconds """ how many seconds should pass minimum between web server restarts. """ CPANEL_RESTART_APACHE_SCRIPT = "/usr/local/cpanel/scripts/restartsrv_httpd" # according to LS docs https://www.litespeedtech.com/docs/webserver/admin LITESPEED_PID_FILE_PATH = Path("/tmp/lshttpd/lshttpd.pid") LITESPEED_RESTART_CMD = ("/usr/local/lsws/bin/lswsctrl", "condrestart") # Recovery needs an unconditional restart: condrestart is a no-op when the # server is down, which is exactly when the hard restart runs. LITESPEED_HARD_RESTART_CMD = ("/usr/local/lsws/bin/lswsctrl", "restart") LITESPEED_CONF_PATH = "/usr/local/lsws/conf/httpd_config.xml" LITESPEED_BIN_PATH = "/usr/local/lsws/bin/litespeed" APACHE2_BIN_PATH = "/usr/sbin/apache2" HTTPD_BIN_PATH = "/usr/sbin/httpd" apache_version_regexp = re.compile(r"Server version:.*(\d+\.\d+\.\d+)") BYTE_SPACES = tuple(x.encode() for x in list(string.whitespace)) APACHE = "apache" logger = logging.getLogger(__name__) class NotRunningError(RuntimeError): """ Error for cases when the web server is expected to be running but it is not. """ class ConfigInvalidError(RuntimeError): """ Error used to indicate that the web server config is having error(s). """ class LiteSpeedConfig: CLIENT_IP_IN_HEADER_TAG = "useIpInProxyHeader" SECURITY_TAG = "security" ACCESS_CONTROL_TAG = "accessControl" ACCESS_CONTROL_ALLOWED_TAG = "allow" ACCESS_CONTROL_DENIED_TAG = "deny" CLIENT_IP_IN_HEADER_DISABLED = 0 CLIENT_IP_IN_HEADER_ENABLED = 1 CLIENT_IP_IN_HEADER_TRUSTED_IP_ONLY = 2 def __init__(self, content): self.config = ET.fromstring(content) def client_ip_in_header(self) -> int: element = self.config.find(self.CLIENT_IP_IN_HEADER_TAG) if element is None or not element.text: return self.CLIENT_IP_IN_HEADER_DISABLED return int(element.text) def set_client_ip_in_header(self, value: int): element = self.config.find(self.CLIENT_IP_IN_HEADER_TAG) if element is None: element = ET.Element(self.CLIENT_IP_IN_HEADER_TAG) self.config.append(element) element.text = str(value) def access_control_allowed_list(self) -> Set[Tuple[str, bool]]: element = self.config.find( "/".join( [ ".", self.SECURITY_TAG, self.ACCESS_CONTROL_TAG, self.ACCESS_CONTROL_ALLOWED_TAG, ] ) ) if element is not None and element.text: return { (item[:-1] if item.endswith("T") else item, item.endswith("T")) for s in element.text.split() for item in s.split(",") if item } return set() def set_access_control_allowed_list(self, allowed): items = [item[0] + "T" if item[1] else item[0] for item in allowed] value = ",".join(items) element = self.config.find( "/".join( [ ".", self.SECURITY_TAG, self.ACCESS_CONTROL_TAG, self.ACCESS_CONTROL_ALLOWED_TAG, ] ) ) if element is None: element = ET.Element(self.ACCESS_CONTROL_ALLOWED_TAG) access_control = self.config.find( "/".join( [ ".", self.SECURITY_TAG, self.ACCESS_CONTROL_TAG, ] ) ) if access_control is None: access_control = ET.Element(self.ACCESS_CONTROL_TAG) security = self.config.find(self.SECURITY_TAG) if security is None: security = ET.Element(self.SECURITY_TAG) self.config.append(security) security.append(access_control) access_control.append(element) element.text = value def tostring(self) -> bytes: buf = io.BytesIO() tree = ET.ElementTree(self.config) tree.write(buf, encoding="utf-8", xml_declaration=True) return buf.getvalue() def _get_litespeed_pid(): """Return LiteSpeed's pid or None if it can't be read.""" with suppress(OSError, ValueError): return int(LITESPEED_PID_FILE_PATH.read_bytes()) def litespeed_running(): """ Litespeed use constant PID file path, so using it to determinate status :return bool """ pid = _get_litespeed_pid() try: return bool(pid and psutil.pid_exists(pid)) except OverflowError: return False def _litespeed_bin() -> str: # /usr/local/lsws/bin is not on the agent service PATH, so which() misses # it there; fall back to the documented install location. return shutil.which("litespeed") or LITESPEED_BIN_PATH def apache_running() -> Optional[str]: """ Finding process with name 'httpd' which belongs to system user. :return str: path to the apache binary if it is running :return None: if apache is not running """ info = _apache_running_process() return info["httpd_bin"] if info else None async def apache_binary_call(*args) -> bytes: httpd_bin = apache_running() if not httpd_bin: raise NotRunningError("Apache is not running") try: if ( OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN and Path("/etc/apache2/envvars").exists() ): # on Debian OS apache requires some env variables # that are set in /etc/apache2/envvars (see DEF-6844) stdout = await check_run( ". /etc/apache2/envvars && {} {}".format( shlex.quote(httpd_bin), shlex.join(args) ), shell=True, ) else: stdout = await check_run([httpd_bin, *args]) except CheckRunError: logger.warning("Apache doesn't work properly") return b"" return stdout def _apache_running_process(*, exclude_users=frozenset()): """ Finding process with name 'httpd' which belongs to system user. Return process info for the apache binary if it is running. Return None if apache is not running """ # Cpanel works on rpm based os and uses packages # according documentation https://documentation.cpanel.net/display/EA4/Apache # noqa # httpd binary is /usr/sbin/httpd # Plesk/Generic uses pkgs from os # so it has /usr/sbin/httpd on rpm based os and /usr/sbin/apache2 on debian # DirectAdmin uses custombuild # It's httpd binary is /usr/sbib/httpd def is_generic_panel_on_apache(): if is_generic_panel_installed(): return IntegrationConfig.get("web_server", "server_type") == APACHE return False if (OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN) and ( is_plesk_installed() or is_generic_panel_on_apache() ): httpd_bin = APACHE2_BIN_PATH else: httpd_bin = HTTPD_BIN_PATH sys_users = set(get_system_user_names()) - exclude_users info = _apache_running_process_info(sys_users) if info: assert info["exe"] is not None info["httpd_bin"] = httpd_bin try: httpd_process_exe = info["exe"] if os.path.samefile(httpd_bin, httpd_process_exe): return info except OSError as exc: logger.info("Can't determine apache bin path: %s", exc) return None def _apache_running_process_info(sys_users): """Retry process_iter() on IndexError.""" for _ in range(2): # retry with suppress(IndexError): return next( ( p.info for p in psutil.process_iter( attrs=["name", "username", "exe", "uids", "gids"] ) if ( p.info["exe"] is not None # non ad_value and p.info["exe"].endswith(("/httpd", "/apache2")) and p.info["username"] in sys_users ) ), None, ) def chown(path): """Make web server user/group own *path*.""" info = _apache_running_process(exclude_users={"root"}) if not info: raise NotRunningError( "Can't find running apache process without root owner." ) os.chown(path, info["uids"][0], info["gids"][0]) def find_running_nginx(): """Return path to a running nginx binary or None if not found.""" return next( ( p.info["exe"] for p in psutil.process_iter(attrs=["name", "username", "exe"]) if ( p.info["name"] is not None # non ad_value and p.info["name"].endswith("nginx") and p.info["exe"] is not None # non ad_value and "nginx" in p.info["exe"] and p.info["username"] in ("nginx", "www-data") ) ), None, ) async def check_with_timeout( webserver_running_cb: Callable[[], Any], timeout_sec=10, granularity: int = 10, ): assert granularity > 0 for _ in range(granularity): result = webserver_running_cb() if result: return result await asyncio.sleep(timeout_sec / granularity) else: return result def is_EA4_available(): """ though, available != running :return bool: """ return os.path.isfile("/etc/cpanel/ea4/is_ea4") def _apache_graceful_restart_cmd(apachectl) -> List[str]: """ :return list: command which can be passed to check_call(..., shell=False) 'apache2 -k graceful' will not work for Ubuntu and will produce 'Invalid Mutex directory in argument file:${APACHE_LOCK_DIR}' error. https://serverfault.com/questions/558283/apache2-config-variable-is-not-defined That is why this specialization for Ubuntu graceful restart. """ # noqa restartsrv_httpd = shutil.which(CPANEL_RESTART_APACHE_SCRIPT) if restartsrv_httpd: # use cpanel specific script if found return [restartsrv_httpd] if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: # see DEF-16795 for details return [ "systemctl", "reload", "--job-mode=replace-irreversibly", os.path.basename(apachectl), ] else: return [apachectl, "-k", "graceful"] def _graceful_restart_cmd_from_integration_conf() -> Optional[Iterable[str]]: if IntegrationConfig.exists(): # Fallback on regular restart techniques # in case of missing restart script. try: restart_script = IntegrationConfig.to_dict()["web_server"][ "graceful_restart_script" ] except KeyError: logger.warning( "Integration config is missing graceful_restart_script field" ) else: if not restart_script: logger.warning( "graceful_restart_script option is empty", ) return None cmd = restart_script.split() if os.path.exists(cmd[0]): return cmd logger.warning( "Web server restart script does not exist: %s", restart_script, ) return None # systemd-run gained --wait (synchronous transient units that propagate the # child's exit code) in v232. CL7/CentOS7 ship systemd 219 and lack it, so # reload confirmation falls back to a config test there. _SYSTEMD_RUN_WAIT_MIN_VERSION = 232 @functools.lru_cache(maxsize=1) def _systemd_run_supports_wait() -> bool: systemd_run = shutil.which("systemd-run") if not systemd_run: return False try: out = check_output([systemd_run, "--version"], stderr=DEVNULL).decode() except (OSError, CalledProcessError): return False match = re.search(r"systemd\s+(\d+)", out) return match is not None and ( int(match.group(1)) >= _SYSTEMD_RUN_WAIT_MIN_VERSION ) def _systemd_run_prefix(wait: bool) -> List[str]: # Do not restart web server in the agent cgroup # (to avoid attaching its processes to it). prefix: List[str] = [] if systemd_run := shutil.which("systemd-run"): prefix += [ systemd_run, "-p", "SendSIGKILL=no", "--slice=graceful_restart", ] if wait and _systemd_run_supports_wait(): prefix.append("--wait") prefix.append("--") return prefix def _graceful_restart_cmd(wait: bool = False) -> Iterable[str]: """Gracefully restart a web server.""" prefix = _systemd_run_prefix(wait) cmd = _graceful_restart_cmd_from_integration_conf() if cmd is not None: return prefix + list(cmd) if litespeed_running(): return prefix + list(LITESPEED_RESTART_CMD) if apachectl := apache_running(): return prefix + _apache_graceful_restart_cmd(apachectl) raise RuntimeError("Could not detect a web server") def _litespeed_installed() -> bool: return os.path.exists(LITESPEED_CONF_PATH) def _apache_systemd_unit() -> Optional[str]: """systemd unit for this host's Apache, or None when the host is not Apache-based. Derived from OS/panel, not a running process — recovery runs precisely when the server is not alive.""" on_generic_apache = False if is_generic_panel_installed(): try: server_type = IntegrationConfig.get("web_server", "server_type") except KeyError: return None if server_type != APACHE: return None on_generic_apache = True if (OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN) and ( is_plesk_installed() or on_generic_apache ): return os.path.basename(APACHE2_BIN_PATH) return os.path.basename(HTTPD_BIN_PATH) def _hard_restart_cmd(wait: bool = True) -> Iterable[str]: """Full (non-graceful) restart to bring a web server back up after a reload left it down. Detects the server by install/config presence (not a running process, which may be down) and raises when no safe command is known (e.g. generic nginx, which has only a graceful integration script). """ prefix = _systemd_run_prefix(wait) if _litespeed_installed(): return prefix + list(LITESPEED_HARD_RESTART_CMD) if restartsrv_httpd := shutil.which(CPANEL_RESTART_APACHE_SCRIPT): return prefix + [restartsrv_httpd, "--restart"] unit = _apache_systemd_unit() if unit is None: raise RuntimeError("No safe hard-restart command for this web server") return prefix + ["systemctl", "restart", unit] def _configtest_cmd() -> Iterable[str]: if is_generic_panel_installed(): try: cmd = IntegrationConfig.get("web_server", "config_test_script") if cmd: return cmd.split() except KeyError: # if setting is not present, fall back to default detection pass if apache_bin := apache_running(): if OsReleaseInfo.id_like() & OsReleaseInfo.DEBIAN: return ["apachectl", "configtest"] return [apache_bin, "-t"] elif litespeed_running(): return [_litespeed_bin(), "-t"] elif nginx_bin := find_running_nginx(): return [nginx_bin, "-t"] raise RuntimeError("Could not detect a web server") _graceful_restart_caller = ContextVar("graceful_restart_caller") async def safe_update_config(config_path, new_config: str) -> bool: """ Update Web-server config with fallback in case of an error happens. It tries to do all the best but because of graceful_restart() the faulty config might still be applied but in practice it is barely probable (because of premature config check). 1. The new config is checked before to be applied. 2. The new config (if checked valid) is atomically applied. 3. The graceful Web-server restart is scheduled. It may hold the actual restart for some time, but it is a required workaround of a litespeed issue. 4. If the Web-server failed to restart the config is reverted. Return value: True if no errors (at least up to the server restart), False if There was an error and config was reverted. Note: It is possible that the config may be reverted even when return value is True. It is because the graceful_restart may delay the actual restart and config may be reverted on that (delayed) stage. """ config_backup_path = os.fspath(config_path) + BACKUP_EXTENSION def remove_backup(): with suppress(FileNotFoundError): os.unlink(config_backup_path) make_backup = os.path.exists(config_path) if not atomic_rewrite(config_path, new_config, backup=make_backup): # nothing has changed => no need to restart return True def revert(): try: os.rename(config_backup_path, config_path) except FileNotFoundError: # truncate file if backup does not exist open(config_path, "w").close() try: await configtest(raise_exception=True) except ConfigInvalidError as e: logger.error("Web server config is invalid: %s", e) revert() else: try: restart_cmd = _graceful_restart_cmd() except RuntimeError as e: logger.error("Failed to get graceful restart command: %s", e) revert() return False loop = asyncio.get_running_loop() def restart_callback(task): def log_config_error(fut): if not fut.cancelled() and fut.exception() is not None: logger.critical( "The reverted config seems to be invalid", exc_info=fut.exception(), ) def log_uncaught_exception(fut): if not fut.cancelled() and fut.exception() is not None: logger.critical( "uncaught exception", exc_info=fut.exception() ) if not task.cancelled() and task.exception() is not None: logger.error( "Web server failed to start... Revert changes back. (%s)", task.exception(), ) revert() task = loop.create_task(configtest(raise_exception=True)) task.add_done_callback(log_config_error) # the least we can do is to try to restart task = loop.create_task(_graceful_restart(restart_cmd)) task.add_done_callback(log_uncaught_exception) else: remove_backup() graceful_restart = webserver_gracefull_restart.coalesce_calls( GRACEFUL_RESTART_MIN_PERIOD, done_callback=restart_callback )(_graceful_restart) caller_frame = inspect.stack()[1] context_token = _graceful_restart_caller.set(caller_frame.function) try: await graceful_restart(restart_cmd) finally: _graceful_restart_caller.reset(context_token) logger.info("Successfully scheduled web server restart") return True return False async def _graceful_restart(restart_cmd=None): """ Gracefully restart a web server. If web server cannot be detected, do nothing. """ _log_graceful_restart_start() try: await check_run(restart_cmd or _graceful_restart_cmd()) except RuntimeError as err: logger.warning("Could not restart a Web server: %s", err) else: logger.info("Successfully restarted web server") @webserver_gracefull_restart.coalesce_calls(GRACEFUL_RESTART_MIN_PERIOD) async def _graceful_restart_coalesced(restart_cmd=None): task = _graceful_restart(restart_cmd) g.web_server_restart_task = task try: return await task finally: g.pop("web_server_restart_task") async def graceful_restart(restart_cmd=None): """ Gracefully restart a web server. If web server cannot be detected, do nothing. """ caller_frame = inspect.stack()[1] context_token = _graceful_restart_caller.set(caller_frame.function) try: result = await _graceful_restart_coalesced(restart_cmd) finally: _graceful_restart_caller.reset(context_token) return result def _log_graceful_restart_start(): caller = _graceful_restart_caller.get("unknown") logger.info("Performing web server graceful restart, from %s", caller) def graceful_restart_sync(): """ Gracefully restart a web server synchronously. If web server cannot be detected, do nothing. """ caller_frame = inspect.stack()[1] context_token = _graceful_restart_caller.set(caller_frame.function) try: _log_graceful_restart_start() finally: _graceful_restart_caller.reset(context_token) try: check_call(_graceful_restart_cmd(), stdout=DEVNULL, stderr=DEVNULL) except RuntimeError as err: logger.warning("Could not restart a Web server: %s", err) else: logger.info("Successfully restarted web server") async def graceful_restart_confirmed() -> bool: """Graceful web-server restart that confirms the reload actually completed and recovers the server if it did not. Unlike graceful_restart() it bypasses the coalesce throttle (the post-update reload must never be dropped); unlike graceful_restart_sync() it observes the reload outcome instead of returning as soon as systemd-run queues the transient unit. """ caller_frame = inspect.stack()[1] context_token = _graceful_restart_caller.set(caller_frame.function) try: _log_graceful_restart_start() finally: _graceful_restart_caller.reset(context_token) try: cmd = _graceful_restart_cmd(wait=True) except RuntimeError as err: logger.warning("Could not restart a Web server: %s", err) return False if await _reload_confirmed(cmd): logger.info("Successfully restarted web server") return True logger.error( "Web server reload after update did not complete cleanly;" " attempting recovery" ) await _log_failed_configtest() if await _reload_confirmed(cmd): logger.info("Web server recovered on graceful reload retry") return True return await _hard_restart() async def _reload_confirmed(cmd) -> bool: """Run *cmd* and report whether the reload truly succeeded. With systemd-run --wait the exit code already reflects completion; on older systemd (no --wait) the reload is fire-and-forget, so fall back to a config test to detect a broken reload. """ try: await check_run(cmd) except (CheckRunError, RuntimeError) as err: logger.warning("Web server reload returned an error: %s", err) return False if _systemd_run_supports_wait(): return True try: await configtest(raise_exception=True) except ConfigInvalidError: return False return True async def _log_failed_configtest() -> None: # The crash is otherwise invisible in the agent log — only Apache's own # error_log records the failed graceful reload. try: await configtest(raise_exception=True) except ConfigInvalidError as err: logger.error("Web server config test failed after update: %s", err) async def _hard_restart() -> bool: try: cmd = _hard_restart_cmd(wait=True) except RuntimeError as err: logger.error("Cannot recover web server: %s", err) return False try: await check_run(cmd) except (CheckRunError, RuntimeError) as err: logger.error("Web server hard restart failed: %s", err) return False logger.info("Web server hard-restarted after failed reload") return True async def configtest(raise_exception=False): """ Check web server's config file. If web server cannot be detected, do nothing. """ logger.info("Performing web server config test") try: await check_run(_configtest_cmd(), raise_exc=ConfigInvalidError) except RuntimeError as err: logger.warning("Could not run configtest: %s", err) if raise_exception: raise ConfigInvalidError("Failed to check config") from err def _parse_apache_version_output(output): match = apache_version_regexp.search(output) if match is not None: return Version(match.group(1)) else: raise ValueError( "Failed to parse apache version string: {}".format(output) ) def _parse_apache_module_list(output: bytes) -> List[bytes]: """ Parse response of httpd -M :param output: stdout of httpd -M (with spaces before module name) Output example: Loaded Modules: core_module (static) so_module (static) http_module (static) mpm_prefork_module (shared) :return: list with installed modules """ return [ line.strip().split()[0] for line in output.splitlines() if line.startswith(BYTE_SPACES) ] def _parse_includes(dump): includes = [] for line in dump.decode().split("\n"): index = line.find("/") if index > 0: includes.append(line[index:].strip()) return includes async def dump_includes(): try: return _parse_includes( await check_run(["apachectl", "-t", "-D", "DUMP_INCLUDES"]) ) except FileNotFoundError: return [] @async_lru_cache(maxsize=1) async def apache_version(): apache_bin = apache_running() if apache_bin is None: raise NotRunningError("Apache is not running") out = await check_run([apache_bin, "-v"]) version = _parse_apache_version_output(out.decode()) logger.info("Apache %s version detected", version) return version @TimedCache( expiration=timedelta( seconds=int( os.environ.get("IMUNIFY360_APACHE_MODULES_CACHE_TIMEOUT", 600) ) ) ) async def apache_modules(): stdout = await apache_binary_call("-M") return _parse_apache_module_list(stdout) defence360agent/utils/0000755000000000000000000000000000000000000011706 5ustar defence360agent/utils/__init__.py0000644000000000000000000020232500000000000014023 0ustar import asyncio import base64 import errno import functools import hashlib import itertools import logging import os import pwd import re import shlex import shutil import signal import stat import subprocess as _subprocess import time import urllib.request from asyncio import Future from collections import OrderedDict, deque from collections.abc import Generator, Iterable from contextlib import ExitStack, contextmanager, suppress from datetime import timedelta from enum import Enum from fcntl import LOCK_EX, LOCK_NB, LOCK_UN, flock from functools import wraps from itertools import islice from pathlib import Path from tempfile import NamedTemporaryFile from typing import ( Any, Awaitable, Callable, Dict, FrozenSet, List, Tuple, TypeVar, ) import async_lru import distro import psutil from peewee import OperationalError from ._shutil import is_safe_subdir_name, rmtree # noqa: F401 from .fd_ops import atomic_rewrite_fd F = TypeVar("F", bound=Callable) logger = logging.getLogger(__name__) USER_IDENTITY_FIELD = "user_id" USER_IDENTITY_HEADERS = ( "User-Agent", "Accept-Language", "Accept-Encoding", "Connection", "DNT", ) _MIN_UID = -1 BACKUP_EXTENSION = ".i360bak" _SYSTEMD_BOOTED_DIR = Path("/run/systemd/system") _CL_SOLO_EDITION_FILE = "/etc/cloudlinux-edition-solo" AV_PID_PATH = Path("/var/run/imunify-antivirus.pid") IM360_NON_RESIDENT_PID_PATH = Path("/var/run/imunify360-agent.pid") IM360_RESIDENT_PID_PATH = Path("/var/run/imunify360.pid") HTTP_REQUEST_RETRY_TIMEOUT = int( os.environ.get("IMUNIFY360_HTTP_REQUEST_RETRY_TIMEOUT", 60) # 1 minute ) class Scope(Enum): AV = "AV only" AV_IM360 = "AV and IM360" IM360 = "IM360 only" IM360_RESIDENT = "IM360 resident only" @functools.lru_cache(maxsize=1) def is_systemd_boot(): """Return True if /run/systemd/system folder exists: [sd_booted] (https://www.freedesktop.org/software/systemd/man/sd_booted.html) """ return ( _SYSTEMD_BOOTED_DIR.exists() and _SYSTEMD_BOOTED_DIR.is_dir() and not _SYSTEMD_BOOTED_DIR.is_symlink() ) @contextmanager def timeit(action, logger_=None, log=None): """ :param str: action name to log :param logging.Logger: logger you want action name and timing to be logged with :param func: log function to use (`log` has preference over `logger_`) """ assert logger_ or log start = time.monotonic() (log or logger_.debug)("%s started", action) yield stop = time.monotonic() (log or logger_.debug)("%s took %.2f second(s)", action, stop - start) def timefun(logger_=logger, action=None, log=None): def decorator(fun): @functools.wraps(fun) async def wrapper(*args, **kwargs): with timeit(action or fun.__name__, logger_=logger_, log=log): return await fun(*args, **kwargs) return wrapper return decorator class sync: """ the same timefun decorator variation but without async/await """ @staticmethod def timefun(logger_=logger, action=None, log=None): """ :param logging.Logger: logger you want action name and timing to be logged with :param str: action name to log """ def decorator(fun): @functools.wraps(fun) def wrapper(*args, **kwargs): with timeit(action or fun.__name__, logger_=logger_, log=log): return fun(*args, **kwargs) return wrapper return decorator async def run( command, stdin=None, stdout=_subprocess.PIPE, stderr=_subprocess.PIPE, shell=False, input=None, **kwargs, ) -> Tuple[int, bytes, bytes]: """Asynchronous command executor. Returns a tuple (exit_code, stdout_data, stderr_data).""" if input is not None: if stdin is not None: # pragma: no cover raise ValueError("stdin and input arguments may not both be used.") stdin = _subprocess.PIPE if shell: assert isinstance(command, str) command = [command] create_subprocess = asyncio.create_subprocess_shell else: assert isinstance(command, (list, tuple)) create_subprocess = asyncio.create_subprocess_exec # type: ignore proc = await retry_on( BlockingIOError, max_tries=2, on_error=await_for(seconds=1) )( create_subprocess )( # type: ignore *command, stdin=stdin, stdout=stdout, stderr=stderr, start_new_session=True, **kwargs, ) out, err = await proc.communicate(input) exit_code = await proc.wait() logger.debug( "run(%s, stdin=%s, shell=%s) = %s", command, stdin, shell, (exit_code, out, err), ) return exit_code, out, err def run_coro(coro, *, loop=None, timeout=None): """Run coroutine from a blocking code (outside the event loop). Coroutine will be wrapped in Task. """ if loop is None: for _ in range(2): try: loop = asyncio.get_event_loop() except RuntimeError: # no loop in the main thread pass else: if not loop.is_closed(): break asyncio.set_event_loop(asyncio.new_event_loop()) return loop.run_until_complete( asyncio.wait_for( coro if isinstance(coro, asyncio.Future) else asyncio.Task(coro), timeout=timeout, ) ) class CheckRunError(_subprocess.CalledProcessError): def __str__(self): _MESSAGE = ( "Command {cmd!r} returned non-zero code {returncode},\n" "\t\tStdout: {output},\n" "\t\tStderr: {error}\n" ) return _MESSAGE.format( cmd=self.cmd, returncode=self.returncode, output=self.output.decode() or None, error=self.stderr.decode() or None, ) async def check_run(command, raise_exc=CheckRunError, **kwargs) -> bytes: """ Asynchronous command executor. Returns output as bytestring. """ returncode, out, err = await run(command, **kwargs) if returncode != 0: raise raise_exc(returncode, command, out, err) return out async def check_exit_code(command, raise_exc=CheckRunError) -> None: """ Asynchronous command executor. Raises raise_exc if exit code is nonzero. Stdin, stdout and stderr of command are connected to /dev/null. """ code, _, _ = await run( command, stdin=_subprocess.DEVNULL, stdout=_subprocess.DEVNULL, stderr=_subprocess.DEVNULL, ) if code != 0: raise raise_exc(code, command) async def safe_run(command, check_returncode=True, **kwargs) -> str: """Safe run command. Returns stdout as string or empty string on error""" try: rc, out, err = await run(command, **kwargs) except OSError: logger.warning("Command %s failed with OSError", command) return "" if check_returncode and rc != 0: logger.warning( "Command %s failed with exit code %s: %s", command, rc, err ) return "" try: result = out.strip().decode() except UnicodeDecodeError: logger.warning("Command %s returned non-utf8 output", command) return "" return result def plainold_lazy_init(decorated_f): """non asyncio vesion of lazy init""" placeholder = None def wrapper(): nonlocal placeholder if placeholder is None: placeholder = decorated_f() return placeholder return wrapper class PeriodicCheck: """ Invoke a callback with a certain period and return cached result in between. Raising an exception from the callback does not affect the next check schedule. """ def __init__(self, cb_coro, check_every_n_seconds): self._cb_coro = cb_coro self._check_every_n_seconds = check_every_n_seconds self._last_check_timestamp = time.monotonic() - check_every_n_seconds self._last_check_result = None self._lock = plainold_lazy_init(asyncio.Lock) async def __call__(self, *args, **kwargs): async with self._lock(): delta = time.monotonic() - self._last_check_timestamp if delta >= self._check_every_n_seconds: logger.debug( "Timeout %d seconds has expired, doing the check: %s", self._check_every_n_seconds, self._cb_coro, ) self._last_check_timestamp = time.monotonic() self._last_check_result = await self._cb_coro(*args, **kwargs) return self._last_check_result def cache_result(nsec): def decorate(coro): return PeriodicCheck(coro, nsec) return decorate class RecurringCheckStop(Exception): """ raised by coroutine to stop recurring_check loop """ pass async def wait_for_period(period, **period_kwargs): try: if callable(period): await asyncio.sleep(period(**period_kwargs)) else: await asyncio.sleep(period) return False except asyncio.CancelledError: return True async def should_stop_after_period_passed(check, period, **period_kwargs): return ( await wait_for_period(period, **period_kwargs) if period and check else False ) def recurring_check( period, consecutive_err_limit=10, check_period_first=False, **period_kwargs ): """ run decorated corotine in a loop every :period: seconds. If more then consecutive_err_limit error occured, exit loop. :param period: :param consecutive_err_limit: :param check_period_first: default false :return: """ def decorator(fun): @wraps(fun) async def wrapped(*args, **kwargs): consecutive_err_cnt = 0 while True: if await should_stop_after_period_passed( check_period_first, period, **period_kwargs ): break try: await fun(*args, **kwargs) except RecurringCheckStop: if "lock_file" in kwargs: try: if isinstance(kwargs["lock_file"], Path): kwargs["lock_file"].unlink() except FileNotFoundError: pass break except asyncio.CancelledError: break except Exception as exc: consecutive_err_cnt += 1 if consecutive_err_cnt > consecutive_err_limit: logger.exception( "Error count exceeded limit,exiting check loop" ) break if isinstance(exc, _subprocess.CalledProcessError): logger.exception( "Failed to run %s (%s). stdout=%s, stderr=%s", exc.cmd, exc.returncode, exc.output, exc.stderr, ) else: logger.exception("Error executing %s", fun) else: consecutive_err_cnt = 0 if await should_stop_after_period_passed( not check_period_first, period, **period_kwargs ): break return wrapped return decorator def atomic_rewrite( filename, data, /, # ^^ positional-only for backward compatibility *, backup: bool | str | os.PathLike = True, uid=None, gid=None, allow_empty_content=True, permissions=None, dir_fd: int | None = None, ) -> bool: """Atomically rewrites *filename* with given *data*. If *filename*'s content is *data* already, do nothing. If both *uid* and *gid* are given then resulting file is chowned to given user id and group id. Skip rewrite with empty content if *allow_empty_content* is False. Chmod to given access *permissions* else preserve *filename* 's permissions. Return True if *filename* file was updated, False otherwise When *dir_fd* is provided it must be an O_NOFOLLOW-opened file descriptor for the parent directory of *filename*. All file I/O is then performed relative to that descriptor, closing the TOCTOU symlink-attack window. *backup* is not supported with *dir_fd*. """ if isinstance(data, str): data = data.encode() if dir_fd is not None: if backup: raise ValueError("backup is not supported when dir_fd is provided") return atomic_rewrite_fd( filename, data, uid=uid, gid=gid, allow_empty_content=allow_empty_content, permissions=permissions, dir_fd=dir_fd, ) with suppress(FileNotFoundError): with open(filename, "rb") as file: old_content = file.read(len(data) + 1) if old_content == data: return False if not allow_empty_content and not data: logger.error("empty content: %r for file: %s", data, filename) return False if backup: if isinstance(backup, (str, os.PathLike)): backup_filename = backup else: backup_filename = os.fspath(filename) + BACKUP_EXTENSION # First-write case: nothing to back up if the target doesn't exist yet. with suppress(FileNotFoundError): shutil.copy(filename, backup_filename) if permissions is None: # get filename's access permissions try: permissions = stat.S_IMODE(os.stat(filename).st_mode) except FileNotFoundError: # input file doesn't exists # derive permissions from umask current_umask = os.umask(0) # can't get it without setting os.umask(current_umask) permissions = 0o666 & ~current_umask dirpath, basename = os.path.split(filename) if not Path(dirpath).exists(): raise FileNotFoundError(f"Parent dir is missing: {dirpath!r}") with ExitStack() as stack: with NamedTemporaryFile( mode="wb", dir=dirpath, suffix=".i360edit", prefix=basename + "_", buffering=0, delete=False, ) as tf: def cleanup(): with suppress(FileNotFoundError): os.remove(tf.name) stack.callback(cleanup) # clean it up in case of any error tf.write(data) tf.flush() if uid is not None and gid is not None: os.chown(tf.fileno(), uid, gid) # note: NamedTemporaryFile always sets 0b600 os.chmod(tf.fileno(), permissions) # avoid partial/empty data on crash os.fsync(tf.fileno()) os.rename(tf.name, filename) stack.pop_all() # success, don't call cleanup # no attempt to ensure that filename is written to disk # (dir is not fsync-ed) return True @functools.lru_cache(1) def os_release_and_version(): try: return Path("/etc/system-release").read_text().rstrip() except OSError: return None def os_version(release_and_version=None) -> str: """Return os version, if can't get it raise ValueError""" rv = release_and_version or os_release_and_version() if rv: match = re.search(r"\s*(\d+\.\d+\S*)(\s|$)", rv) if match: return match.group(1) else: os_release_and_version.cache_clear() raise ValueError("Can't discover os version from %r" % rv) class OsReleaseInfo: ETC_OS_RELEASE = "/etc/os-release" DEBIAN = frozenset(("debian",)) RHEL_FEDORA_CENTOS = frozenset(("rhel", "fedora", "centos")) UNKNOWN = frozenset(("unknown",)) dict_ = None @classmethod def dict_from_file(cls, dict_): with open(cls.ETC_OS_RELEASE) as f: for line in f: try: k, v = line.rstrip().split("=") dict_[k] = v.strip('"') except ValueError: pass if "ID_LIKE" in dict_: dict_["ID_LIKE"] = frozenset(dict_["ID_LIKE"].split()) else: # https://www.freedesktop.org/software/systemd/man/os-release.html#ID= dict_["ID_LIKE"] = frozenset((dict_.get("ID", "linux"),)) @classmethod def to_dict(cls) -> Dict[str, Any]: if cls.dict_ is None: dict_: Dict[str, Any] = dict() if os.path.exists(cls.ETC_OS_RELEASE): cls.dict_from_file(dict_) else: # centos and cl 6 does not have /etc/os-release file # this will need to move to distro package in python 3.8 d = distro.linux_distribution() if d and d[0]: osid = d[0].lower().split()[0] if osid == "red" and "Red Hat Enterprise Linux" in d[0]: osid = "rhel" dict_["ID"] = osid dict_["PRETTY_NAME"] = "{} {} ({})".format( d[0], d[1], d[2] ) if osid in ("cloudlinux", "centos", "rhel"): dict_["ID_LIKE"] = cls.RHEL_FEDORA_CENTOS elif osid in ("ubuntu", "debian"): dict_["ID_LIKE"] = cls.DEBIAN else: dict_["ID_LIKE"] = cls.UNKNOWN else: dict_["ID"] = "unknown" dict_["ID_LIKE"] = cls.UNKNOWN dict_["PRETTY_NAME"] = "unknown" cls.dict_ = dict_ return cls.dict_ @classmethod def id_like(cls) -> FrozenSet[str]: return cls.to_dict()["ID_LIKE"] @classmethod def pretty_name(cls) -> str: return cls.to_dict()["PRETTY_NAME"] @classmethod def get_os(cls) -> str: """ :return: OS name, like centos, ubuntu, debian, cloudlinux, redhat in lower case """ return cls.to_dict().get("ID", "unknown") @classmethod def is_rhel(cls): return cls.get_os() == "rhel" @classmethod def is_centos(cls): return cls.get_os() == "centos" @classmethod def is_ubuntu(cls): return cls.get_os() == "ubuntu" @classmethod def is_cloudlinux(cls): return cls.get_os() in ("cloudlinux", "cloudlinuxserver") @classmethod def is_cloudlinux_solo(cls): return os.path.exists(_CL_SOLO_EDITION_FILE) @classmethod def is_debian(cls): return cls.get_os() == "debian" @classmethod def is_oracle_linux(cls): return cls.get_os() == "ol" @classmethod def is_almalinux(cls): return cls.get_os() == "almalinux" @classmethod def is_rockylinux(cls): return cls.get_os() == "rocky" def file_hash( filename: str, hash_func=hashlib.md5, chunksize: int = 4096 ) -> str: """Return hash of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. """ return file_hash_and_size(filename, hash_func, chunksize)[0] def file_hash_and_size( filename: str, hash_func, chunksize: int = 4096, ) -> Tuple[str, int]: """Calculate hash and size of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. Return tuple(hash, file size).""" hash_ = hash_func() size = 0 with open(filename, "rb") as f: while True: chunk = f.read(chunksize) if not chunk: break hash_.update(chunk) size += len(chunk) return hash_.hexdigest(), size def _parse_name_value(varname, defs_line): """Given login.defs line, return *varname*'s value.""" name, value = defs_line.split() # no end of line comments if varname != name: raise ValueError("Expected {varname!r}, got {name!r}".format(**vars())) return value def get_min_uid(): global _MIN_UID if _MIN_UID == -1: _MIN_UID, _ = _get_max_min_uid() return _MIN_UID def _get_max_min_uid(path="/etc/login.defs"): """Get UID_MIN, UID_MAX from the login.defs file specified as *path*. On error, return default for the current OS values. """ uid_min, uid_max = 1000, 60000 try: with open(path) as file: for line in file: if line.startswith("UID_MIN"): uid_min = int(_parse_name_value("UID_MIN", line)) if line.startswith("UID_MAX"): uid_max = int(_parse_name_value("UID_MAX", line)) except (OSError, ValueError): # use default pass return uid_min, uid_max def get_non_system_users( excludes=("imunify360-captcha", "imunify360-webshield"), ): """ :param excludes: users to exclude in results :return: list: list of pwd.struct_passwd objects representing users """ uid_min, uid_max = _get_max_min_uid() return [ entry for entry in pwd.getpwall() if uid_min <= entry.pw_uid <= uid_max and entry.pw_name not in excludes ] def get_system_user_names(): """ :return: list: list of str with system user names """ uid_min, _ = _get_max_min_uid() return [ entry.pw_name for entry in pwd.getpwall() if uid_min >= entry.pw_uid ] @functools.lru_cache() def is_system_user(uid: int): uid_min, uid_max = _get_max_min_uid() return uid < uid_min async_lru_cache = functools.partial( async_lru.alru_cache, maxsize=100, # set tot true because of backward compatibility with previous # implementation of async_lru_cache typed=True, ) def append_with_newline(filename, data): with open(filename, "r+") as f: # ensure we have eol at the end of file # returns poiner position 0 if file is empty last_char_pos = f.seek(0, 2) if last_char_pos != 0: f.seek(last_char_pos - 1) if f.read(1) != "\n": f.write("\n") f.write(data) if not data.endswith("\n"): f.write("\n") def append_with_newline_bytes(filename: os.PathLike, data: bytes) -> None: """Append *data* to *filename* making sure there is \n at the end.""" with open(filename, "r+b") as f: # ensure we have eol at the end of file # returns poiner position 0 if file is empty last_char_pos = f.seek(0, 2) if last_char_pos != 0: f.seek(last_char_pos - 1) if f.read(1) != b"\n": f.write(b"\n") f.write(data) if not data.endswith(b"\n"): f.write(b"\n") def ensure_line_in_file(filename, line): """Add *line* to *filename* if it is not present in the file Returns: True if the file was changed, False otherwise. """ changed = False with open(filename, "r") as f: if not any(_line.strip() == line for _line in f): changed = True if changed: append_with_newline(filename, line) return changed def ensure_line_in_file_bytes(filename: os.PathLike, line: bytes) -> bool: """Add *line* to *filename* if it is not present in the file. Returns: True if the file was changed, False otherwise. """ changed = False with open(filename, "rb") as f: if not any(_line.strip() == line for _line in f): changed = True if changed: append_with_newline_bytes(filename, line) return changed def remove_line_from_file(filename, line): basedir = os.path.dirname(filename) with ( open(filename, "r") as sf, NamedTemporaryFile(mode="w", dir=basedir, delete=False) as tf, ): for _line in sf: if _line.strip() != line: tf.write(_line) os.rename(tf.name, filename) class FileLock: """ Simple context manager to enable UNIX-specific file locking with flock system call """ _TIMEOUT = 10 # Default timeout to wait for lock def __init__(self, path, timeout=_TIMEOUT): self.path = path self.locked = False self.file = open(path, "w") self.timeout = timeout async def __aenter__(self): start = time.time() while True: try: # Trying to perform file lock flock(self.file, LOCK_EX | LOCK_NB) self.locked = True return self # Resource temporarily unavailable except (OSError, IOError) as ex: if ex.errno != errno.EAGAIN: raise # if did not succeed # to lock file within a given timeout # perform operation without it elif self.timeout < time.time() - start: logger.warning( "Failed to lock file %s. Timeout exceeded.", self.path ) break # Return control to event loop and wait await asyncio.sleep(1) async def __aexit__(self, exc_type, exc_val, exc_tb): # If successfully locked file at entering context # release it if self.locked: flock(self.file, LOCK_UN) self.locked = False self.file.close() def user_identity(attackers_ip, source, fields=USER_IDENTITY_HEADERS): try: # TODO: change after migtration to python3.8 # dicts in python3.5 do not keep order, # that's why we sort items to get the same hash for the same source uid_data = [attackers_ip] uid_data.extend( str(value) for field, value in sorted(source.items()) if field in fields ) # ModSecurity has no capability to create sha256 hashes # using sha1 instead hash_alg = hashlib.sha1() hash_alg.update("".join(uid_data).encode("utf8", "surrogateescape")) return hash_alg.hexdigest() except (ValueError, UnicodeEncodeError) as e: logger.error( "Generation of user identity hash failed, invalid data: %s", e ) return None def is_root_user(): return os.getuid() == 0 @contextmanager def run_with_umask(mask: int): current_mask = os.umask(mask) try: yield finally: os.umask(current_mask) def get_abspath_from_user_dir(username: str, relpath="") -> Path: """ Returns user's home dir if `relpath` is not specified. Otherwise, returns absolute path of `relpath` build from `username`'s home dir :raise ValueError: when user home dir is not exists """ if not isinstance(username, str): raise ValueError("Invalid type for %s, should be str!" % username) if os.sep in username: raise ValueError("Invalid username") try: pw = pwd.getpwnam(username) except KeyError: raise ValueError("User {!r} doesn't exist".format(username)) abs_path = os.path.join(pw.pw_dir, relpath) return Path(abs_path) def does_path_belong_to_user(path: str, username: str) -> bool: status = False try: user_home = get_abspath_from_user_dir(username) Path(path).relative_to(user_home) status = True except ValueError as e: logger.warning(str(e)) return status def get_path_owner(path): if not os.path.abspath(path): raise ValueError("Path %s should be absolute!" % path) while True: if os.path.exists(path): try: return pwd.getpwuid(os.stat(path).st_uid).pw_name except KeyError: return str(os.stat(path).st_uid) path = os.path.dirname(path) def split_for_chunk(iterable: Iterable, chunk_size: int = 500) -> Generator: """ Generator that splits iterable on N-parts by chunk_size items in each chunk >>> list(split_for_chunk([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], chunk_size=2)) [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] :param iterable: :param int chunk_size: :return: generator: """ i = iter(iterable) piece = list(islice(i, chunk_size)) while piece: yield piece piece = list(islice(i, chunk_size)) def freeze(d): if isinstance(d, dict): return frozenset((key, freeze(value)) for key, value in d.items()) elif isinstance(d, list): return tuple(freeze(value) for value in d) return d class Singleton(type): """ Metaclass for creating only one instance of class, when providing the same arguments. """ _instances = {} def __call__(cls, *args, **kwargs): key = (cls, freeze(args), freeze(kwargs)) if not cls._instances.get(key): cls._instances[key] = super(Singleton, cls).__call__( *args, **kwargs ) return cls._instances[key] @functools.lru_cache(maxsize=10) def get_external_ip(): """ :return str: server's external IP address """ with urllib.request.urlopen("https://api.ipify.org", timeout=2) as r: return r.read().decode() def get_kernel_module_parameter(module_name, parameter): """ Reads parameter of kernel module from /sys/module/{module_name}/parameters/{parameter} :return str: value of the parameter """ _MOD_PAR_PATH = "/sys/module/{mod}/parameters/{parameter}" param_file = _MOD_PAR_PATH.format(mod=module_name, parameter=parameter) if not os.path.exists(param_file): raise ValueError( "Cannot find parameter %s for module %s" % (parameter, module_name) ) with open(param_file, "r") as p: value = p.read().strip() return value def dict_deep_update(dst, src, allow_overwrite=True) -> bool: """Performs deep update of dict dst with values from src. Does not overwrite subdicts in dst blindly with new dicts in src, but does a deep update of (sub)dict content recursively""" updated = False for k, v in src.items(): if isinstance(v, dict): if k not in dst or not v: dst[k] = v updated = True else: updated = dict_deep_update(dst[k], v) else: assert ( k not in dst or allow_overwrite ), f"{k} already exists in {dst}" dst[k] = v updated = True return updated class TimedCache: def __init__(self, expiration, maxsize=100): assert isinstance(expiration, timedelta) self.expiration = expiration self.maxsize = maxsize self.cache = OrderedDict() self._locks = {} def _collect(self): """Clear cache from expired values""" tmp_cache = OrderedDict() for key in self.cache: value, added_at = self.cache[key] if (time.time() - added_at) < self.expiration.total_seconds(): tmp_cache[key] = value, added_at self.cache = tmp_cache def cache_clear(self): self.cache = OrderedDict() self._locks = {} def _make_key(self, args, kwargs): """ Generate key from call arguments :param args: call positional args :param kwargs: call keyword args :return: """ seed = args if kwargs: kw = sorted(kwargs.items()) seed += tuple(kw) return hash(seed) def __call__(self, func: F) -> F: """ Use it to cache calls to decorated function @TimedCache(expiration=timedelta(minutes=10)) async def func(*args, **kwargs): pass :param func: decorated function :return: NOTE: is not thread safe. """ @wraps(func) async def wrapper_async(*args, **kwargs): key = self._make_key(args, kwargs) lock = self._locks.get(key) if lock is None: lock = self._locks[key] = asyncio.Lock() while True: try: await asyncio.wait_for( lock.acquire(), self.expiration.total_seconds() ) break except asyncio.TimeoutError: # if TimeoutError occurred it means that we not able to # acquire lock, and if it the same lock which we try to # acquire just create a new one, otherwise it already # recreated and we should repeat the attempt to acquire # a lock if lock is self._locks[key]: lock = self._locks[key] = asyncio.Lock() else: lock = self._locks[key] try: self._collect() try: result, _ = self.cache[key] except KeyError: if len(self.cache) >= self.maxsize: self.cache.popitem(last=False) result = await func(*args, **kwargs) self.cache[key] = result, time.time() finally: lock.release() return result @wraps(func) def wrapper_sync(*args, **kwargs): self._collect() key = self._make_key(args, kwargs) try: result, _ = self.cache[key] except KeyError: if len(self.cache) >= self.maxsize: self.cache.popitem(last=False) result = func(*args, **kwargs) self.cache[key] = result, time.time() return result wrapper = ( wrapper_async if asyncio.iscoroutinefunction(func) else wrapper_sync ) wrapper.cache_clear = self.cache_clear # type: ignore return wrapper # type: ignore timed_cache = TimedCache async def safe_cancel_task(task, *, timeout=5): """Cancel *task* and wait up to *timeout* seconds for it to finish. Unlike the common ``task.cancel(); suppress(CancelledError); await task`` pattern, this function **always returns** within *timeout* seconds — even if the task catches ``CancelledError`` and continues running (see DEF-40570 / CPython #103486). Uses ``asyncio.wait`` (not ``wait_for``) because ``wait_for`` also hangs when the inner task survives cancellation. """ if task.done(): # Retrieve exception to suppress "Task exception was never retrieved" if not task.cancelled(): try: task.result() except Exception: pass return task.cancel() done, _ = await asyncio.wait({task}, timeout=timeout) if done: exc = task.exception() if not task.cancelled() else None if exc: logger.warning("Task %r raised during cancellation: %s", task, exc) elif not task.done(): logger.warning( "Task %r did not finish within %ds after cancel", task, timeout ) task.add_done_callback( lambda t: log_future_errors( t, message="Abandoned task failed after cancel timeout" ) ) def fail_agent_service(): """ Send SIGUSR2 to os.getpid() to shutdown agent process by signal (implies exit code -12). Agent will do failover restart then thanks to systemd (or chkservd) if it needs. """ os.kill(os.getpid(), signal.SIGUSR2) async def run_cmd_and_log(cmd, log_file_mask, **popen_kwargs): """ Runs command and log it's output to the log file :param cmd: :param log_file_mask: :return: str path of log file """ live_log = log_file_mask.replace("*", str(os.getpid())) with open(live_log, "w") as live_log_fp: popen_kwargs.update( dict( stdin=asyncio.subprocess.DEVNULL, stdout=live_log_fp, stderr=live_log_fp, start_new_session=True, ) ) logger.debug("Popen(%r, %r)", cmd, popen_kwargs) proc = await asyncio.subprocess.create_subprocess_shell( cmd, **popen_kwargs ) with open(live_log + ".pid", "w") as pf: pf.write( "{:d}\t{}\n".format( proc.pid, psutil.Process(proc.pid).create_time().hex() ) ) return live_log # DEF-41613: NoNewPrivileges=true on the agent units propagates to every # descendant and refuses execve() that would require new privileges — # setuid bits, file capabilities, *or* an LSM (SELinux/AppArmor) domain # transition. RPM %prein and apt postinst scriptlets routinely trip the # LSM-transition path: exec'ing /bin/sh from imunify360_t fails with # EPERM ("Operation not permitted") on AlmaLinux 8/9, CloudLinux 8/9 # (SELinux) and similarly on Debian (AppArmor). AmbientCapabilities= # only compensates for the capability half of NNP, not the LSM half. # # To keep the MR's main security goal (NNP) while letting the few agent # subprocesses that drive package installs/removes work, we re-launch # those specific subprocesses as transient units via systemd-run. They # become children of PID 1 instead of the agent, so they don't inherit # NNP, ProtectSystem= or the rest of the agent's sandbox. @functools.lru_cache(maxsize=1) def _has_no_new_privs() -> bool: """Return True iff this process has PR_SET_NO_NEW_PRIVS=1. Used to decide whether to wrap package-management subprocesses in systemd-run. On systemd<231 hosts (CL7) the MR's compat drop-in resets NoNewPrivileges=no, so the wrap is unnecessary and would also fail (CL7 ships systemd 219, no --pipe/--wait support). """ try: with open("/proc/self/status") as f: for line in f: if line.startswith("NoNewPrivs:"): return line.split()[1] == "1" except OSError: pass return False _SYSTEMD_RUN_BASE = ( "systemd-run", "--quiet", "--wait", "--pipe", "--collect", "--property=NoNewPrivileges=no", "--property=ProtectSystem=no", ) def _systemd_run_setenv_args(env): if not env: return () return tuple(f"--setenv={k}={v}" for k, v in env.items()) def _wrap_outside_sandbox_shell(cmd: str, env=None) -> str: """Wrap a shell command so it runs as a transient systemd unit outside the agent's NoNewPrivileges= sandbox. Returns the original command unchanged when this process is not under NNP.""" if not _has_no_new_privs(): return cmd parts = ( _SYSTEMD_RUN_BASE + _systemd_run_setenv_args(env) + ("/bin/sh", "-c", cmd) ) return " ".join(shlex.quote(p) for p in parts) def _wrap_outside_sandbox_argv(argv, env=None): """Argv-form counterpart of _wrap_outside_sandbox_shell.""" if not _has_no_new_privs(): return list(argv) return list( _SYSTEMD_RUN_BASE + _systemd_run_setenv_args(env) + ("--",) + tuple(argv) ) async def run_cmd_and_log_outside_sandbox( cmd, log_file_mask, *, env=None, **popen_kwargs ): """run_cmd_and_log variant that escapes the agent's systemd sandbox. Use for shell commands whose RPM/apt scriptlets perform LSM domain transitions on exec (e.g. kernelcare install, hardened-php groupinstall) — see the module-level NNP note above. """ return await run_cmd_and_log( _wrap_outside_sandbox_shell(cmd, env=env), log_file_mask, **popen_kwargs, ) async def run_outside_sandbox(argv, *, env=None, **kwargs): """run() variant that escapes the agent's systemd sandbox.""" return await run(_wrap_outside_sandbox_argv(argv, env=env), **kwargs) async def check_run_outside_sandbox(argv, *, env=None, **kwargs): """check_run() variant that escapes the agent's systemd sandbox.""" return await check_run(_wrap_outside_sandbox_argv(argv, env=env), **kwargs) # A package transaction left in the agent's own cgroup is charged against the # CPUQuota= and MemoryHigh= that the unit's ExecStartPre applies to it, and a # dnf dependency solve plus an SELinux policy rebuild runs to several hundred # MB. Handing the command to systemd-run makes PID 1 create the unit, so it # lands in system.slice and its usage is neither throttled by our quota nor # counted as ours. # # This is a different question from the sandbox escape above and must not # share its gate: the resource isolation is needed whether or not the unit # currently sets NoNewPrivileges=, so it depends only on systemd-run being # able to host the command. systemd-run gained --wait in 232, --pipe in 235 # and --collect in 236, so this is inert on EL7's systemd 219. _SYSTEMD_RUN_MIN_VERSION = 236 @functools.lru_cache(maxsize=1) def _systemd_run_supported() -> bool: """Return True iff systemd-run can host a transient unit for us.""" if not is_systemd_boot(): return False try: version_line = _subprocess.run( ["systemd-run", "--version"], stdout=_subprocess.PIPE, stderr=_subprocess.DEVNULL, text=True, timeout=30, ).stdout except (OSError, _subprocess.SubprocessError): return False match = re.search(r"\d+", version_line) if match is None: return False return int(match.group()) >= _SYSTEMD_RUN_MIN_VERSION def _wrap_in_own_cgroup_shell(cmd: str, env=None) -> str: """Wrap a shell command so PID 1 owns its cgroup, keeping its CPU and memory off the agent's. Returns the command unchanged where systemd-run cannot host it.""" if not _systemd_run_supported(): return cmd parts = ( _SYSTEMD_RUN_BASE + _systemd_run_setenv_args(env) + ("/bin/sh", "-c", cmd) ) return " ".join(shlex.quote(p) for p in parts) async def run_cmd_and_log_in_own_cgroup( cmd, log_file_mask, *, env=None, **popen_kwargs ): """run_cmd_and_log variant that keeps the command's resource usage out of the agent's cgroup. Use for package transactions heavy enough to matter against the agent's own CPU and memory allowance. """ return await run_cmd_and_log( _wrap_in_own_cgroup_shell(cmd, env=env), log_file_mask, **popen_kwargs, ) # fix AttributeError: 'NoneType' object has no attribute '_PENDING' on exit # https://github.com/python/asyncio/issues/423#issuecomment-268882753 class Task(asyncio.Task): def __del__(self): if self._state == "PENDING" and self._log_destroy_pending: context = { "task": self, "message": "Task was destroyed but it is pending!", } if self._source_traceback: context["source_traceback"] = self._source_traceback self._loop.call_exception_handler(context) try: Future.__del__(self) except AttributeError: name = getattr(self._coro, "__qualname__", None) or getattr( self._coro, "__name__", None ) code = getattr(self._coro, "gi_code", None) or getattr( self._coro, "cr_code", None ) frame = getattr(self._coro, "gi_frame", None) or getattr( self._coro, "cr_frame", None ) filename = code.co_filename lineno = (frame and frame.f_lineno) or code.co_firstlineno print( "!> Finalizer error in {}() {} at {} line {}".format( name, self._state, filename, lineno ) ) def await_for(seconds): """Return async callback which waits for *seconds*. Usage: @retry_on(Error, on_error=await_for(seconds=PAUSE_INTERVAL), timeout=T) async def coro(): 'here's something that may raise Error.' """ async def pause(*args): return await asyncio.sleep(seconds) return pause def retry_on( exception, on_error=None, max_tries=None, timeout=None, silent=False, log=None, should_retry=None, ): """ Retry the function call on exception (or exceptions, if given in tuple) at most *max_tries*. Await *on_error* (if set) for each exception. If *timeout* is set, stop all attempts in *timeout* seconds. If *silent* is set to True - don't raise exceptions after max If *should_retry* is set - await it and on False, stop auto-retry cycle tries or timeout. """ if not any([max_tries, timeout]): raise ValueError("Set any of max_tries, timeout") def decorator(func): @functools.wraps(func) async def wrapper_async(*args, **kwargs): if timeout: end_time = time.monotonic() + timeout for i in ( itertools.count(1) if not max_tries else range(1, max_tries + 1) ): try: if timeout: remaining_time = end_time - time.monotonic() if remaining_time > 0: return await asyncio.wait_for( func(*args, **kwargs), timeout=remaining_time ) else: if not silent: raise asyncio.TimeoutError elif log: log.error( "Timeout exceeded when calling %s", func ) else: return await func(*args, **kwargs) except (asyncio.TimeoutError, asyncio.CancelledError): raise except exception as exc: if should_retry is not None: should_retry_ret = await should_retry(exc, i) if not should_retry_ret: i = max_tries if i == max_tries: if not silent: raise elif log: log.error( "Max tries exceeded when calling %s with" " error %s", func, exc, ) if on_error is not None: await on_error(exc, i) @functools.wraps(func) def wrapper_sync(*args, **kwargs): if timeout: end_time = time.monotonic() + timeout for i in ( itertools.count(1) if not max_tries else range(1, max_tries + 1) ): try: if timeout: remaining_time = end_time - time.monotonic() if remaining_time > 0: return func(*args, **kwargs) else: if not silent: raise TimeoutError elif log: log.error( "Timeout exceeded when calling %s", func ) else: return func(*args, **kwargs) except exception as exc: if should_retry is not None: should_retry_ret = should_retry(exc, i) if not should_retry_ret: i = max_tries if i == max_tries: if not silent: raise elif log: log.error( "Max tries exceeded when calling %s with" " error %s", func, exc, ) if on_error is not None: on_error(exc, i) if asyncio.iscoroutinefunction(func): return wrapper_async else: return wrapper_sync return decorator def stub_unexpected_error(func): """If func throws an exception it is catched, converted to a string and returned as a result of a call.""" @functools.wraps(func) async def wrapper_async(*args, **kwargs): try: return await func(*args, **kwargs) except Exception as e: # noqa return repr(e) @functools.wraps(func) def wrapper_sync(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: # noqa return repr(e) return wrapper_async if asyncio.iscoroutinefunction(func) else wrapper_sync def log_error_and_ignore(exception=Exception, log_handler=None): """A decorator that logs uncaught exceptions ignoring them otherwise. CancelledError is not handled. """ if log_handler is None: log_handler = logger.error def decorator(coro): @functools.wraps(coro) async def wrapper_async(*args, **kwargs): try: return await coro(*args, **kwargs) except asyncio.CancelledError: raise except exception as e: log_handler( "Ignoring exception from %s: %s", getattr(coro, "__qualname__", "coro"), e, ) @functools.wraps(coro) def wrapper_sync(*args, **kwargs): try: return coro(*args, **kwargs) except exception as e: log_handler( "Ignoring exception from %s: %s", getattr(coro, "__qualname__", "coro"), e, ) if asyncio.iscoroutinefunction(coro): return wrapper_async else: return wrapper_sync return decorator def abort_agent_on(exception, abort=fail_agent_service): """Abort the agent service on *exception*.""" def decorator(coro): @functools.wraps(coro) async def wrapper(*args, **kwargs): try: return await coro(*args, **kwargs) except exception as e: logger.exception(e) # do not silently stop the current task but abort() return wrapper return decorator def snake_case(string): """PascalCase to snake_case""" return re.sub("([a-z])([A-Z])", r"\1_\2", string).lower() CHUNK_SIZE_SQL_QUERY = 200 # SQLite WAL reports SQLITE_BUSY_SNAPSHOT as "database is locked"; unlike # vanilla SQLITE_BUSY it is not covered by PRAGMA busy_timeout, so retry it. # Backoff 50/100/200/400/800 ms (~1.5s worst case) stays under the 10s # busy_timeout the connection is configured with. DB_LOCK_MAX_RETRIES = 5 DB_LOCK_RETRY_BACKOFF_BASE = 0.05 DB_LOCK_RETRY_BACKOFF_MAX = 1.0 def _is_db_locked_error(exc) -> bool: return "locked" in str(exc).lower() def get_results_iterable_expression( expr, iterable, *args, exec_expr_with_empty_iter=False ): """ Get iterator over results of sql expression expr. Given iterable will be split for chunks and we will return iterator containing results of all split queries. Useful for sql selects with in_() in order to avoid too many sql variables error. If exec_expr_with_empty_iter is True and iterable is None(empty) we will process expression once, passing here chunk=None expr(None, *args) :param expr: :param iterable: :param exec_expr_with_empty_iter: if iterable is None(empty) process given expression once, passing here chunk=None expr(None, *args) :return: """ if not iterable and exec_expr_with_empty_iter: chunks = [None] else: chunks = split_for_chunk(iterable, chunk_size=CHUNK_SIZE_SQL_QUERY) from defence360agent.model import instance with instance.db.transaction(): for chunk in chunks: yield from expr(chunk, *args) def execute_iterable_expression( expr, iterable, *args, chunk_size=CHUNK_SIZE_SQL_QUERY ): """ Get number of results of sql expression expr. Given iterable will be split for chunks and we will return number of results of all split queries. Useful for sql delete with in_() in order to avoid too many sql variables error. The iterable is materialized BEFORE the database transaction opens, and the transaction is retried on transient SQLite lock errors. This matters because callers commonly pass a generator that does its own SELECTs (e.g. ``MalwareHit.delete_hits(get_outdated_entries(...))``): in SQLite WAL mode, the read snapshot taken inside a transaction becomes stale as soon as another writer commits, and the subsequent write raises SQLITE_BUSY_SNAPSHOT, which PRAGMA busy_timeout does *not* cover. """ chunks = list(split_for_chunk(iterable, chunk_size=chunk_size)) from defence360agent.model import instance def _backoff(exc, attempt): backoff = min( DB_LOCK_RETRY_BACKOFF_BASE * (2 ** (attempt - 1)), DB_LOCK_RETRY_BACKOFF_MAX, ) logger.warning( "SQLite lock contention, retrying in %.3fs (retry %d/%d): %s", backoff, attempt, DB_LOCK_MAX_RETRIES, exc, ) time.sleep(backoff) @retry_on( OperationalError, on_error=_backoff, max_tries=DB_LOCK_MAX_RETRIES + 1, should_retry=lambda exc, attempt: _is_db_locked_error(exc), ) def _execute_all(): changed = 0 with instance.db.transaction(): for chunk in chunks: changed += expr(chunk, *args).execute() return changed return _execute_all() def encode_filename(file): return os.fsencode(file.replace("\n", "\\n")) + b"\n" def decode_filename(file): return os.fsdecode(file)[:-1].replace("\\n", "\n") def base64_encode_filename(path: Path) -> bytes: return base64.b64encode(os.fsencode(path)) def base64_decode_filename(b64name: bytes) -> Path: return Path(os.fsdecode(base64.b64decode(b64name))) def getpwnam(username): """ Like pwd.getpwnam(username) but returns None instead of raising KeyError. """ try: result = pwd.getpwnam(username) except KeyError: result = None return result def clip(value, low, high): """ Put the specified `value` inside the [`low`, `high`] interval. """ return max(min(value, high), low) def log_future_errors(fut, log_handler=None, message="Background task failed"): """ Callback for asyncio.Future that logs exceptions and ignores CancelledError. Use this as a done_callback for asyncio tasks/futures: future.add_done_callback(log_future_errors) Or with custom logging: future.add_done_callback( lambda f: log_future_errors(f, logger.warning, "Upload failed") ) """ if log_handler is None: log_handler = logger.warning try: fut.result() except asyncio.CancelledError: pass except Exception as e: log_handler("%s: %s", message, e) def create_task_and_log_exceptions( loop, coro: Callable[..., Awaitable], *args, **kwargs ): """ Use this function in plugin initialization instead of loop.create_task to be able to see the exceptions from the specified coroutine. """ def _log_exception(task): if not task.cancelled() and task.exception() is not None: loop.call_exception_handler( { "message": ( "Unhandled exception during plugin initialization!" ), "exception": task.exception(), "task": task, } ) new_task = loop.create_task(coro(*args, **kwargs)) new_task.add_done_callback(_log_exception) return new_task def make_coro(function): """ Create coroutine from regular function Useful to pass functions to APIs requiring coroutines Note: coroutine will still block event loop in main thread. For most blocking functions, run_in_executor should be considered instead :param function: :return: coroutine running function """ async def coro(*args, **kwargs): return function(*args, **kwargs) return coro COPY_TO_MODSEC_MAXTRIES = 5 _MODSEC_COPY_FAILURE_TIMEOUT = 5 async def log_failed_to_copy_to_modsec(exc, i): if i == COPY_TO_MODSEC_MAXTRIES: log = logger.error else: log = logger.warning log( "Failed to copy data%s to modsec ruleset dir %r, try: %s", f" ({fn})" if (fn := getattr(exc, "filename", None)) else "", exc, i, ) await asyncio.sleep(_MODSEC_COPY_FAILURE_TIMEOUT) async def readlines_from_cmd_output( cmd: List[str], *, err_buf_size=100, **popen_kwargs ): """ Start *cmd*, yield its stdout line by line [b'\n'] If *cmd* return nonzero exit status, raise CheckRunError with the last *err_buf_size* lines from stderr. """ async def read_pipe_into(pipe, buf): async for line in pipe: buf.append(line) err_buf = deque(maxlen=err_buf_size) # keep a few last lines proc = await asyncio.create_subprocess_exec( *cmd, start_new_session=True, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, **popen_kwargs, ) try: # note: read data from stderr to avoid deadlock # if stderr pipe buffer is full asyncio.create_task(read_pipe_into(proc.stderr, err_buf)) async for line in proc.stdout: # type: ignore yield line finally: returncode = await proc.wait() if returncode != 0: raise CheckRunError(returncode, cmd, b"", b"".join(err_buf)) async def finally_happened(predicate_coro, *args, max_tries=2, delay=5): """ Retry *predicate_coro(*args)* until it becomes true, but no more than *max_tries* attempts. Sleep for *delay* seconds before the next *predicate_coro()* call. Return whether the predicate became true. """ for attempt in range(1, max_tries + 1): result = await predicate_coro(*args) if not result and attempt < max_tries: await asyncio.sleep(delay) continue return result async def nice_iterator(iterable, chunk_size=10_000): """Yield to the event loop every *chunk_size* iterations.""" # for chunks in zip(*[iter(iterable)]*chunk_size): # yield from chunks # -> SyntaxError: 'yield from' inside async function for i, item in enumerate(iterable, start=1): yield item if (i % chunk_size) == 0: await asyncio.sleep(0) class LazyLock: """ Descriptor object to share async Lock between client objects. Used in order to achieve lazy evaluation of the lock and share state between it's clients. Using asyncio.Lock in client code directly: >>> class Foo: >>> lock = asyncio.Lock() leads to an unclear error ([Errno 9] Bad file descriptor), when trying to move this Lock during demonization process. """ def __init__(self): self._lock = None def __get__(self, instance, owner): if not self._lock: self._lock = asyncio.Lock() return self._lock def _parse_rpm_line(line: str) -> tuple[str, str] | None: """Parse RPM output line, return (package_name, version) or None if not installed.""" line = line.strip() if not line or "not installed" in line.lower() or ": " not in line: return None pkg_name, version = line.split(": ", 1) return pkg_name, version def _parse_dpkg_line(line: str) -> tuple[str, str] | None: """Parse dpkg-query output line, return (package_name, version) or None if not installed.""" line = line.strip() if not line or "no packages found" in line.lower() or ": " not in line: return None # Status format: "pkg: version desired_action current_status error_flag" # e.g., "vim: 2:8.2 install ok installed" or "pkg: 1.0 hold ok installed" # Only consider package installed if status ends with "ok installed" # (not "not-installed" which also ends with "installed") if not line.endswith(" ok installed"): return None pkg_name, rest = line.split(": ", 1) # Version is the first token (rest contains "version status...") version = rest.split()[0] if rest else "" return pkg_name, version @functools.lru_cache(maxsize=1) def _get_package_query_cmd() -> ( tuple[list[str], Callable[[str], tuple[str, str] | None]] ): if OsReleaseInfo.is_ubuntu() or OsReleaseInfo.is_debian(): return ( [ "dpkg-query", "--show", "--showformat", "${Package}: ${Version} ${Status}\n", ], _parse_dpkg_line, ) return ( [ "rpm", "-q", "--queryformat=%{NAME}: %{VERSION}-%{RELEASE}.%{ARCH}\n", ], _parse_rpm_line, ) class FirewallDisabledException(Exception): """Exception in case of using firewall api, when it's disabled""" def check_disabled_firewall(func): @wraps(func) async def wrapper(*args, **kwargs): if os.path.exists("/var/imunify360/firewall_disabled"): raise FirewallDisabledException( "Not available in the current build" ) return await func(*args, **kwargs) return wrapper IMUNIFY_PACKAGE_NAMES = frozenset( { "imunify-ui", "imunify360-firewall", "imunify-antivirus", "imunify-core", } ) async def system_packages_info( packages: Iterable[str], ) -> dict[str, str | None]: """ Retrieves the version of the specified system packages using a command and regex specific to the current system. Parameters: packages (Iterable[str]): A set of package names to retrieve version for. Returns: A dictionary mapping package names to their corresponding version strings, or None if the package is not installed or version information cannot be retrieved. """ cmd, parse_line = _get_package_query_cmd() packages_list = list(packages) output = await safe_run_with_timeout( cmd + packages_list, timeout=30, check_returncode=False ) return _parse_package_info_output(output, packages_list, parse_line) def _parse_package_info_output( output: str, packages: list[str], parse_line: Callable[[str], tuple[str, str] | None], ) -> dict[str, str | None]: parsed = { pkg: ver for line in output.splitlines() if (result := parse_line(line)) and (pkg := result[0]) and (ver := result[1]) } return {pkg: parsed.get(pkg) for pkg in packages} async def safe_run_with_timeout( command, timeout, log=logger.error, **kwargs ) -> str: try: return await asyncio.wait_for( safe_run(command, **kwargs), timeout=timeout ) except asyncio.TimeoutError: log("Command %s failed: Timeout occurred", command) return "" def batched(iterable, n: int): # backported from Python 3.12, except it yields a list instead of a tuple # https://docs.python.org/3.12/library/itertools.html#itertools.batched # # batched('ABCDEFG', 3) → ABC DEF G if n < 1: raise ValueError("n must be at least one") it = iter(iterable) while batch := list(islice(it, n)): yield batch def batched_dict(d: Dict[Any, Any], n: int): for batch in batched(d, n): yield {k: d[k] for k in batch} @functools.lru_cache(maxsize=1) def is_cloudways(): try: hostname = _subprocess.check_output( ["hostname", "-f"], text=True ).strip() _is_cloudways = hostname.endswith( (".cloudwaysapps.com", ".cloudwaysstagingapps.com") ) if not _is_cloudways and Path("/usr/local/sbin/apm").exists(): result = _subprocess.check_output( ["/usr/local/sbin/apm", "info"], text=True ) if "Cloudways" in result: _is_cloudways = True return _is_cloudways except Exception as e: logger.error("Error while checking environment: %s", e) return False def write_pid_file(pid_file: Path) -> int: pid = os.getpid() if not pid_file or str(pid_file) == "": return pid try: pid_file.write_text(f"{pid}\n") return pid except Exception as e: logger.error("Error while creatin PID file: %s", e) return pid def cleanup_pid_file(pid_file: Path): if not pid_file or str(pid_file) == "": return None try: if pid_file.exists(): pid_file.unlink() return except Exception as e: logger.error("Error while cleanup PID file: %s", e) return async def backoff_sleep(exception, attempt): """ Used with retry_on decorator as on_error handler: Example: ``` @retry_on( PanelException, on_error=backoff_sleep, timeout=_HTTP_REQUEST_RETRY_TIMEOUT, ) def some_function(): ... ``` """ logger.warning("#%s sleep on: %s", attempt, exception) await asyncio.sleep(2 << attempt) defence360agent/utils/__pycache__/0000755000000000000000000000000000000000000014116 5ustar defence360agent/utils/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000031323500000000000021325 0ustar r_j V ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl Z ddlZddlZddlZddlmZddlmZmZddlmZmZddlmZmZmZddlmZddl m!Z!ddl"m#Z#m$Z$m%Z%m&Z&dd lm'Z'dd lm(Z(dd l)m*Z*dd l+m,Z,dd l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5ddl6Z6ddl7Z7ddl8Z8ddl9m:Z:ddl;mm?Z?e5de0Z@ejAeBZCdZDdZEdaFdZGe*dZHdZIe*dZJe*dZKe*dZLeMejNOddZPGdd e!ZQejRd!d"ZSedd#ZTeCddfd$ZUGd%d&ZVdejWejWd'dfd(e4eMeXeXffd)ZYddd*d+ZZGd,d-ej[Z\e\fd(eXfd.Z]e\fdd/Z^dd(e_fd1Z`d2ZaGd3d4Zbd5ZcGd6d7edZed8Zfd9Zg dd;Zhd0ddd0ddd<d=eie_zejjzd>eMdzd(eifd?ZkejRdd@Zldd(e_fdAZmGdBdCZnejodDfdEe_dFeMd(e_fdGZp ddEe_dFeMd(e4e_eMffdHZqdIZrdJZsddLZt ddNZudOZvejRdPeMfdQZwejxe6jydRd0SZzdTZ{dEejjdUeXd(dfdVZ|dWZ}dEejjdXeXd(eifdYZ~dZZGd[d\ZeEfd]Zd^Zed_eMfd`Zddbe_d(e*fdcZdde_dbe_d(eifdeZdfZddhedieMd(efdjZdkZGdldmeZejRd:!dnZdoZdd(eifdpZGdqdrZeZdsdtduZdvZdwZejRd!d(eifdxZdyZdzZdd{e_d(e_fd|Zdd}Zdd~dZdd~dZdd~dZdZejRd!d(eifdZdd{e_d(e_fdZdd~dZGddejZdZ ddZdZeddfdZefdZdZdZdsZdZdZd(eifdZd'ddZeddZdZdZdde*d(eXfdZdeXd(e*fdZdZdZddZde0de/ffdZdZdsZdsZdZdRdd{e3e_fdZddsddZddZGddZdXe_d(ee_e_fdzfdZdXe_d(ee_e_fdzfdZejRd!d(eee_e0e_gee_e_fdzfffdZGddedZdZehdZdee_d(ee_e_dzffdZde_dee_de0e_gee_e_fdzfd(ee_e_dzffdZeCjfd(e_fdZdeMfdZde1e.e.fdeMfdZejRd!dZde*d(eMfdZde*fdZd„ZdS)N)Future) OrderedDictdeque) GeneratorIterable) ExitStackcontextmanagersuppress) timedelta)Enum)LOCK_EXLOCK_NBLOCK_UNflockwraps)islice)Path)NamedTemporaryFile)Any AwaitableCallableDict FrozenSetListTupleTypeVar)OperationalError)is_safe_subdir_namermtree)atomic_rewrite_fdF)bounduser_id)z User-AgentzAccept-LanguagezAccept-Encoding ConnectionDNTz.i360bakz/run/systemd/systemz/etc/cloudlinux-edition-soloz/var/run/imunify-antivirus.pidz/var/run/imunify360-agent.pidz/var/run/imunify360.pid%IMUNIFY360_HTTP_REQUEST_RETRY_TIMEOUT<ceZdZdZdZdZdZdS)ScopezAV onlyz AV and IM360z IM360 onlyzIM360 resident onlyN)__name__ __module__ __qualname__AVAV_IM360IM360IM360_RESIDENTS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/__init__.pyr,r,Hs" BH E*NNNr5r,)maxsizecto2tot S)zReturn True if /run/systemd/system folder exists: [sd_booted] (https://www.freedesktop.org/software/systemd/man/sd_booted.html) )_SYSTEMD_BOOTED_DIRexistsis_dir is_symlinkr4r5r6is_systemd_bootr=OsC ""$$ 1  & & ( ( 1#..00 0r5c#K|s|sJtj}|p|jd|dVtj}|p|jd|||z dS)z :param str: action name to log :param logging.Logger: logger you want action name and timing to be logged with :param func: log function to use (`log` has preference over `logger_`) z %s startedNz%s took %.2f second(s))time monotonicdebug)actionlogger_logstartstops r6timeitrG\sz c> N  ESGM<000 EEE >  DSGM3VTE\JJJJJr5cfd}|S)NcNtjfd}|S)NcKtpj5|i|d{VcdddS#1swxYwYdSN)rCrDrGr-argskwargsrBfunrDrCs r6wrapperz+timefun..decorator..wrapperns.#,SIII 2 2 S$1&11111111 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2s 8<< functoolsrrPrQrBrDrCs` r6 decoratorztimefun..decoratormsH    2 2 2 2 2 2 2   2r5r4rCrBrDrUs``` r6timefunrWls0 r5c0eZdZdZeeddfdZdS)synczF the same timefun decorator variation but without async/await Ncfd}|S)z :param logging.Logger: logger you want action name and timing to be logged with :param str: action name to log cNtjfd}|S)Ncztpj5|i|cdddS#1swxYwYdSrKrLrMs r6rQz0sync.timefun..decorator..wrappersF2clGMMM003///000000000000000000s 044rRrTs` r6rUzsync.timefun..decoratorsH _S ! ! 0 0 0 0 0 0 0" ! 0Nr5r4rVs``` r6rWz sync.timefun}s0       r5)r-r.r/__doc__ staticmethodloggerrWr4r5r6rYrYxsEt\r5rYFreturnc $K||tdtj}|r't|tsJ|g}t j}n*t|ttfsJt j }ttdtd|||||dd|d{V}| |d{V\} } |d{V} td |||| | | f| | | fS) zYAsynchronous command executor. Returns a tuple (exit_code, stdout_data, stderr_data).Nz/stdin and input arguments may not both be used.r)seconds) max_trieson_errorTstdinstdoutstderrstart_new_sessionz run(%s, stdin=%s, shell=%s) = %s) ValueError _subprocessPIPE isinstancestrasynciocreate_subprocess_shelllisttuplecreate_subprocess_execretry_onBlockingIOError await_for communicatewaitr_rA) commandrgrhrishellinputrOcreate_subprocessprocouterr exit_codes r6runrs   NOO O  ;'3''''')#;'D%=11111#:1y/C/C/C             D%%e,,,,,,,,HCiikk!!!!!!I LL*  C  c3 r5)looptimeoutc|rtdD]b} tj}|sn7n#t$rYnwxYwtjtjc|tjt|tj r|ntj ||S)zjRun coroutine from a blocking code (outside the event loop). Coroutine will be wrapped in Task. Nrbr) rangerpget_event_loop is_closed RuntimeErrorset_event_loopnew_event_looprun_until_completewait_forrnrTask)cororr_s r6run_corors  |q = =A -//~~''E       "7#9#;#; < < < <  " "tW^44 LDD',t:L:L     s? A  A ceZdZdZdS) CheckRunErrorcd}||j|j|jpd|jpdS)Nz[Command {cmd!r} returned non-zero code {returncode}, Stdout: {output}, Stderr: {error} )cmd returncodeoutputerror)formatrrrdecoderi)self_MESSAGEs r6__str__zCheckRunError.__str__s_ $  ;%%''/4+$$&&.$    r5N)r-r.r/rr4r5r6rrs#      r5rc`Kt|fi|d{V\}}}|dkr||||||S)zJ Asynchronous command executor. Returns output as bytestring. Nr)r)rz raise_excrOrrrs r6 check_runrsZ "%W!7!7!7!7777777JSQi GS#666 Jr5cKt|tjtjtjd{V\}}}|dkr |||dS)z Asynchronous command executor. Raises raise_exc if exit code is nonzero. Stdin, stdout and stderr of command are connected to /dev/null. )rgrhriNr)rrlDEVNULL)rzrcoders r6check_exit_codersy !"" JD!Q qyyig&&&yr5TcK t|fi|d{V\}}}n,#t$rtd|YdSwxYw|r%|dkrtd|||dS |}n,#t $rtd|YdSwxYw|S)zGSafe run command. Returns stdout as string or empty string on errorNzCommand %s failed with OSErrorrz'Command %s failed with exit code %s: %sz#Command %s returned non-utf8 output)rOSErrorr_warningstriprUnicodeDecodeError)rzcheck_returncoderOrcrrresults r6safe_runrs 33F33333333 C 7AAArrB!GG 5wC   r##%% .wrapper!s  %+--Kr5r4)rrQrs` @r6plainold_lazy_initrs.K Nr5ceZdZdZdZdZdS) PeriodicCheckz Invoke a callback with a certain period and return cached result in between. Raising an exception from the callback does not affect the next check schedule. c||_||_tj|z |_d|_t tj|_ dSr) _cb_coro_check_every_n_secondsr?r@_last_check_timestamp_last_check_resultrrpLock_lock)rcb_corocheck_every_n_secondss r6__init__zPeriodicCheck.__init__3sD &;#%)^%5%58M%M""&' 55 r5cK|4d{Vtj|jz }||jkrVt d|j|jtj|_|j|i|d{V|_|jcdddd{VS#1d{VswxYwYdS)Nz3Timeout %d seconds has expired, doing the check: %s) rr?r@rrr_rArr)rrNrOdeltas r6__call__zPeriodicCheck.__call__<s\::<< + + + + + + + +N$$t'AAE333 I/M .2^-=-=*0= t0Nv0N0N*N*N*N*N*N*N'* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +sBB33 B=B=N)r-r.r/r]rrr4r5r6rr*s<666 + + + + +r5rcfd}|S)Nc$t|Sr)r)rnsecs r6decoratezcache_result..decorateKsT4(((r5r4)rrs` r6 cache_resultrJs#))))) Or5ceZdZdZdS)RecurringCheckStopz: raised by coroutine to stop recurring_check loop Nr-r.r/r]r4r5r6rrQs Dr5rcK t|r!tj|di|d{Vntj|d{VdS#tj$rYdSwxYw)NFTr4)callablerpsleepCancelledError)period period_kwargss r6wait_for_periodrYs F   (- 7 7 7 788 8 8 8 8 8 8 8 8-'' ' ' ' ' ' ' 'u  !ttsA AA#"A#c8K|r|rt|fi|d{VndSNF)r)checkrrs r6should_stop_after_period_passedrdsG   of66 666666666 r5 c fd}|S)z run decorated corotine in a loop every :period: seconds. If more then consecutive_err_limit error occured, exit loop. :param period: :param consecutive_err_limit: :param check_period_first: default false :return: cFtfd}|S)NcKd} tfid{VrdS |i|d{Vd}n#t$rOd|vrG t|dtr|dn#t $rYnwxYwYdSt j$rYdSt$r}|dz }|kr t dYd}~dSt|tj r3t d|j |j|j|jnt dYd}~nd}~wwxYwt fid{VrdST)NrT lock_filerz-Error count exceeded limit,exiting check loopz+Failed to run %s (%s). stdout=%s, stderr=%szError executing %s)rrrnrunlinkFileNotFoundErrorrpr Exceptionr_ exceptionrlCalledProcessErrorrrrri) rNrOconsecutive_err_cntexccheck_period_firstconsecutive_err_limitrPrrs r6wrappedz3recurring_check..decorator..wrappedysH"# ' 8&2?E,#t.v.........:+,''9*"f,,!)&*=tDD= &{ 3 : : < < <0!!! D!EE-EE DDD'1,'*-BBB((K!#{'EFF D((IGNJJ (()=sCCC!D&9**F6CEO' sK0D?5A54D?5 B?D?BD?D? D?"%D: A(D::D?r)rPrrrrrs` r6rUz"recurring_check..decoratorxsI s) ) ) ) ) ) ) )  ) Vr5r4)rrrrrUs```` r6recurring_checkrls7--------^ r5)backupuidgidallow_empty_content permissionsdir_fdrrc t|tr|}|'|rtdt |||||||St t 5t|d5}|t|dz} dddn #1swxYwY| |kr ddddS dddn #1swxYwY|s |st d||dS|rt|ttj fr|} ntj|tz} t t 5t!j|| dddn #1swxYwY|k t%jtj|j}n>#t $r1tjd} tj| d | z}YnwxYwtj|\} } t1| st d | t55}t7d | d | d zdd5fd}||||*|(tj ||tj! |tj" dddn #1swxYwYtj#j$||%dddn #1swxYwYdS)aAtomically rewrites *filename* with given *data*. If *filename*'s content is *data* already, do nothing. If both *uid* and *gid* are given then resulting file is chowned to given user id and group id. Skip rewrite with empty content if *allow_empty_content* is False. Chmod to given access *permissions* else preserve *filename* 's permissions. Return True if *filename* file was updated, False otherwise When *dir_fd* is provided it must be an O_NOFOLLOW-opened file descriptor for the parent directory of *filename*. All file I/O is then performed relative to that descriptor, closing the TOCTOU symlink-attack window. *backup* is not supported with *dir_fd*. Nz/backup is not supported when dir_fd is provided)rrrrrrbrFzempty content: %r for file: %srizParent dir is missing: wbz .i360editr)modedirsuffixprefix bufferingdeletectt5tjjddddS#1swxYwYdSr)r rosremovename)tfsr6cleanupzatomic_rewrite..cleanups/00''Ibg&&&''''''''''''''''''s=AAT)&rnroencoderkr"r ropenreadlenr_rrPathLikefspathBACKUP_EXTENSIONshutilcopystatS_IMODEst_modeumaskpathsplitrr:rrcallbackwriteflushchownfilenochmodfsyncrenamerpop_all)filenamedatarrrrrrfile old_contentbackup_filename current_umaskdirpathbasenamestackrrs @r6atomic_rewriters6${{}}   PNOO O   3#     # $ $ (D ! ! 3T))CIIM22K 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 $     t 5tXFFFu 3 fsBK0 1 1 E$OO i114DDO ' ( ( 3 3 K/ 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1,rwx'8'8'@AAKK  1 1 1HQKKM H] # # #=.0KKK  1  h//GX ==   ! !G E' E EFFF  c>     " ' ' ' ' ' NN7 # # # HHTNNN HHJJJ3?c3/// HRYY[[+ . . . HRYY[[ ! ! !- " " " " " " " " " " " " " " ". "'8$$$ 38 4s(C9&B+ C+B/ /C2B/ 3 CCCE<<FF +F668G10G1M>0B=L9- M>9L= =M>L= 1M>>NNc tdS#t$rYdSwxYw)Nz/etc/system-release)r read_textrstriprr4r5r6os_release_and_versionr"sP)**4466==??? tts25 AAc|p t}|r-tjd|}|r|dSntt d|z)z3Return os version, if can't get it raise ValueErrorz\s*(\d+\.\d+\S*)(\s|$)rz!Can't discover os version from %r)r"researchgroup cache_clearrk)release_and_versionrvmatchs r6 os_versionr+st  8 6 8 8B - 3R88  ";;q>> ! " **,,, 82= > >>r5ceZdZdZedZedZedZdZe dZ e de e e ffdZe dee fd Ze de fd Ze de fd Ze d Ze d Ze dZe dZe dZe dZe dZe dZe dZdS) OsReleaseInfoz/etc/os-release)debian)rhelfedoracentos)unknownNct|j5}|D]U} |d\}}|d||<F#t $rYRwxYw dddn #1swxYwYd|vr,t |d|d<dSt |ddf|d<dS)N="ID_LIKEIDlinux)rETC_OS_RELEASEr!r rrk frozensetget)clsdict_flinekvs r6dict_from_filezOsReleaseInfo.dict_from_file.s3 #$ % %   ;;==..s33DAq wws||E!HH!D                    (y)9)?)?)A)ABBE)    )%))D'*B*B)DEEE)   s5A;AAA; A+(A;*A++A;;A?A?r`c\|jt}tj|jr||ntj}|r|dr|d d}|dkr d|dvrd}||d<d |d|d|d|d <|d vr |j |d <n.|d vr |j |d <n|j|d <nd |d<|j|d <d |d <||_|jS)NrredzRed Hat Enterprise Linuxr/r7z {} {} ({})rrb PRETTY_NAME) cloudlinuxr1r/r6)ubuntur.r2)r=dictrr r:r9rBdistrolinux_distributionlowerr rRHEL_FEDORA_CENTOSDEBIANUNKNOWN)r<r=dosids r6to_dictzOsReleaseInfo.to_dict=sQ 9 $(FFEw~~c011 5""5))))-//515Q4::<<--//2Du}})Cqt)K)K%"&E$K+7+>+>!adAaD,,E-(???+.+Ai((!555+.:i((+.;i(("+E$K'*{E)$+4E-(CIyr5c6|dS)Nr6rQr<s r6id_likezOsReleaseInfo.id_like\s{{}}Y''r5c6|dS)NrErSrTs r6 pretty_namezOsReleaseInfo.pretty_name`s{{}}]++r5cR|ddS)zi :return: OS name, like centos, ubuntu, debian, cloudlinux, redhat in lower case r7r2)rQr;rTs r6get_oszOsReleaseInfo.get_osds" {{}}  y111r5c2|dkS)Nr/rYrTs r6is_rhelzOsReleaseInfo.is_rhellszz||v%%r5c2|dkS)Nr1r[rTs r6 is_centoszOsReleaseInfo.is_centospzz||x''r5c2|dkS)NrGr[rTs r6 is_ubuntuzOsReleaseInfo.is_ubuntutr_r5c.|dvS)N)rFcloudlinuxserverr[rTs r6 is_cloudlinuxzOsReleaseInfo.is_cloudlinuxxszz||AAAr5cJtjtSr)rr r:_CL_SOLO_EDITION_FILErTs r6is_cloudlinux_soloz OsReleaseInfo.is_cloudlinux_solo|sw~~3444r5c2|dkS)Nr.r[rTs r6 is_debianzOsReleaseInfo.is_debianr_r5c2|dkS)Nolr[rTs r6is_oracle_linuxzOsReleaseInfo.is_oracle_linuxszz||t##r5c2|dkS)N almalinuxr[rTs r6 is_almalinuxzOsReleaseInfo.is_almalinuxszz||{**r5c2|dkS)Nrockyr[rTs r6 is_rockylinuxzOsReleaseInfo.is_rockylinuxszz||w&&r5)r-r.r/r9r:rMrLrNr= classmethodrBrrorrQrrUrWrYr\r^rardrgrirlrorrr4r5r6r-r-&s&N Y{ # #F"#?@@i %%G E F F[ FS#X[<( #((([(,C,,,[,2s222[2&&[&(([((([(BB[B55[5(([($$[$++[+''['''r5r-r chunksizec0t|||dS)zReturn hash of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. r)file_hash_and_size)r hash_funcrus r6 file_hashrys h 9 = =a @@r5c|}d}t|d5} ||}|sn(|||t|z }@ dddn #1swxYwY||fS)aCalculate hash and size of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. Return tuple(hash, file size).rrTN)rrupdater hexdigest)rrxruhash_sizer>chunks r6rwrws IKKE D h   FF9%%E  LL    CJJ D    ??  d ""sAA,,A03A0c|\}}||kr&tdjdit|S)z0Given login.defs line, return *varname*'s value.z"Expected {varname!r}, got {name!r}r4)r rkrvars)varname defs_linervalues r6_parse_name_valuersJ//##KD%$D=DNNtvvNNOOO Lr5cHtdkrt\a}tS)Nr()_MIN_UID_get_max_min_uid)rs r6 get_min_uidrs2~~&(( ! Or5/etc/login.defschd\}} t|5}|D]f}|drttd|}|drttd|}g dddn #1swxYwYn#tt f$rYnwxYw||fS)zGet UID_MIN, UID_MAX from the login.defs file specified as *path*. On error, return default for the current OS values. )ii`UID_MINUID_MAXN)r startswithintrrrk)r uid_minuid_maxrr?s r6rrs( #GW  $ZZ F4 F F??9--F!"3It"D"DEEG??9--F!"3It"D"DEEG  F F F F F F F F F F F F F F F F Z       G s5BA*B  B BBBBB-,B-zimunify360-captchazimunify360-webshieldclt\fdtjDS)z~ :param excludes: users to exclude in results :return: list: list of pwd.struct_passwd objects representing users cPg|]"}|jcxkrknn |jv |#Sr4pw_uidpw_name).0entryexcludesrrs r6 z(get_non_system_users..sS     el - - - -g - - - - -%-x2O2O 2O2O2Or5rpwdgetpwall)rrrs`@@r6get_non_system_usersrsR())GW      \^^   r5cdt\}fdtjDS)z; :return: list: list of str with system user names c4g|]}|jk |jSr4r)rrrs r6rz)get_system_user_names..s.   W 5L5L 5L5L5Lr5r)rrs @r6get_system_user_namesrsE"##JGQ    #&<>>   r5rc0t\}}||kSr)r)rrrs r6is_system_userrs'))GW =r5d)r7typedct|d5}|dd}|dkrF||dz |ddkr|d|||ds|dddddS#1swxYwYdS)Nzr+rrbr rseekrr endswithrrr> last_char_poss r6append_with_newliners h   q! A   FF=1$ % % %vvayyD     }}T""  GGDMMM                  B"CCCrct|d5}|dd}|dkrF||dz |ddkr|d|||ds|dddddS#1swxYwYdS)z>Append *data* to *filename* making sure there is at the end.zr+brrbr Nrrs r6append_with_newline_bytesrs h   !q! A   FF=1$ % % %vvayyE!!  }}U##  GGENNN                  rcd}t|d5}tfd|Dsd}dddn #1swxYwY|rt||S)zAdd *line* to *filename* if it is not present in the file Returns: True if the file was changed, False otherwise. Frc3HK|]}|kVdSrrr_liner?s r6 z&ensure_line_in_file..)088U5;;==D(888888r5TN)ranyrrr?changedr>s ` r6ensure_line_in_filer!s G h  8888a88888 G,Hd+++ N>AAr?cd}t|d5}tfd|Dsd}dddn #1swxYwY|rt||S)zAdd *line* to *filename* if it is not present in the file. Returns: True if the file was changed, False otherwise. Frc3HK|]}|kVdSrrrs r6rz,ensure_line_in_file_bytes..8rr5TN)rrrrs ` r6ensure_line_in_file_bytesr0s G h  8888a88888 G2!(D111 Nrctj|}t|d5}t d|d5}|D]/}||kr||0tj|j|dddn #1swxYwYddddS#1swxYwYdS)NrwF)rrr) rr dirnamerrrr rr)rr?basedirsfrrs r6remove_line_from_filer?s5gooh''G Xs%!???%CE  E{{}}$$ "'8$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%s6B4A B B4B B4#B $B44B8;B8c,eZdZdZdZefdZdZdZdS)FileLockz` Simple context manager to enable UNIX-specific file locking with flock system call rcZ||_d|_t|d|_||_dS)NFr)r lockedrrr)rr rs r6rzFileLock.__init__Ss*  sOO  r5cKtj} t|jttzd|_|S#t tf$r}|jtj kr|j tj|z kr&t d|j Yd}~dStjdd{VYd}~nd}~wwxYw)NTz)Failed to lock file %s. Timeout exceeded.r)r?rrr rrrIOErrorerrnoEAGAINrr_rr rpr)rrEexs r6 __aenter__zFileLock.__aenter__Ys  ' 'di7!2333"  W% ' ' '8u|++\DIKK%$777NNCTYEEEEEmA&&&&&&&&&&&&&& ' 's*ACAC/CCcK|jrt|jtd|_|jdSr)rrrrclose)rexc_typeexc_valexc_tbs r6 __aexit__zFileLock.__aexit__qsC ; & $)W % % %  r5N)r-r.r/r]_TIMEOUTrrrr4r5r6rrKsZ H%- '''0r5rc |g}|fdt|Dtj}|d|dd|S#ttf$r%}t d|Yd}~nd}~wwxYwdS)Nc3DK|]\}}|v t|VdSr)ro)rfieldrfieldss r6rz user_identity..s?  u JJ  r5rutf8surrogateescapez9Generation of user identity hash failed, invalid data: %s) extendsorteditemshashlibsha1r{joinrr|rkUnicodeEncodeErrorr_r) attackers_ipsourceruid_datahash_alges ` r6 user_identityr{s  !>    &v||~~ 6 6      <>>))009JKKLLL!!### * +    G         4sB%B))C:CCc0tjdkSNr)rgetuidr4r5r6 is_root_userrs 9;;! r5maskc#Ktj|} dVtj|dS#tj|wxYwr)rr )r current_masks r6run_with_umaskrsM8D>>L  s 2Arusernamec~t|tstd|ztj|vrtd t j|}n0#t$r#td|wxYwtj |j |}t|S)z Returns user's home dir if `relpath` is not specified. Otherwise, returns absolute path of `relpath` build from `username`'s home dir :raise ValueError: when user home dir is not exists z#Invalid type for %s, should be str!zInvalid usernamezUser {!r} doesn't exist) rnrorkrseprgetpwnamKeyErrorrr rpw_dirr)rrelpathpwabs_paths r6get_abspath_from_user_dirrs h $ $K>IJJJ v+,,,E \( # # EEE299(CCDDDEw||BIw//H >>s A-Br cd} t|}t||d}n>#t$r1}tt |Yd}~nd}~wwxYw|S)NFT)rr relative_torkr_rro)r rstatus user_homers r6does_path_belong_to_userrs F-h77  T y))) s1vv Ms38 A3'A..A3ctj|std|z tj|rg t jtj|jj S#t$r)ttj|jcYSwxYwtj |})NzPath %s should be absolute!) rr abspathrkr:rgetpwuidrst_uidrrrorr s r6get_path_ownerr s 7??4 ?6=>>>% 7>>$   1 1|BGDMM$899AA 1 1 1274==/00000 1wt$$ %s/B0B65B6iterable chunk_sizec#Kt|}tt||}|r%|Vtt||}|#dSdS)a Generator that splits iterable on N-parts by chunk_size items in each chunk >>> list(split_for_chunk([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], chunk_size=2)) [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] :param iterable: :param int chunk_size: :return: generator: N)iterrrr)r r ipieces r6split_for_chunkrsp XA :&& ' 'E , VAz**++ ,,,,,r5ct|tr+td|DSt|trt d|DS|S)Nc3>K|]\}}|t|fVdSrfreeze)rkeyrs r6rzfreeze..s1JJ*#u#ve}}-JJJJJJr5c34K|]}t|VdSrr)rrs r6rzfreeze..s(22uVE]]222222r5)rnrHr:rrrrs)rOs r6rrsm!T3JJ JJJJJJ At  322222222 Hr5c&eZdZdZiZfdZxZS) Singletonzc Metaclass for creating only one instance of class, when providing the same arguments. c|t|t|f}|j|s(tt|j|i||j|<|j|Sr)r _instancesr;superrr)r<rNrOr __class__s r6rzSingleton.__call__srF4LL&..1~!!#&& "@% 3"7"7"@###CN3 ~c""r5)r-r.r/r]rr __classcell__)rs@r6rrsI J#########r5rctjdd5}|cdddS#1swxYwYdS)z3 :return str: server's external IP address zhttps://api.ipify.orgrbrN)urllibrequesturlopenrr)rs r6get_external_ipr#s    7  C C!qvvxx  !!!!!!!!!!!!!!!!!!s&AAAc<d}|||}tj|st d|d|t |d5}|}dddn #1swxYwY|S)z Reads parameter of kernel module from /sys/module/{module_name}/parameters/{parameter} :return str: value of the parameter z(/sys/module/{mod}/parameters/{parameter})mod parameterzCannot find parameter z for module rN)rrr r:rkrrr) module_namer& _MOD_PAR_PATH param_fileprs r6get_kernel_module_parameterr+s ?M%%+%KKJ 7>>* % % j8A ;; O    j#  !!  !!!!!!!!!!!!!!! Ls'BBBcd}|D][\}}t|tr%||vs|s|||<d}(t|||}?||vs|sJ|d||||<d}\|S)zPerforms deep update of dict dst with values from src. Does not overwrite subdicts in dst blindly with new dicts in src, but does a deep update of (sub)dict content recursivelyFTz already exists in )rrnrHdict_deep_update)dstsrcallow_overwriteupdatedr@rAs r6r-r-s G  1 a   ||1|A*3q6155  ---- /CFGG Nr5c8eZdZd dZdZdZdZdedefdZd S) TimedCacherct|tsJ||_||_t |_i|_dSr)rnr expirationr7rcache_locks)rr5r7s r6rzTimedCache.__init__*s<*i00000$  ]]  r5ct}|jD]J}|j|\}}tj|z |jkr||f||<K||_dS)zClear cache from expired valuesN)rr6r?r5 total_seconds)r tmp_cacherradded_ats r6_collectzTimedCache._collect1shMM : 1 1C"joOE8 h&$/*G*G*I*III!& # r5c:t|_i|_dSr)rr6r7rs r6r'zTimedCache.cache_clear:s ]]  r5c|}|r3t|}|t|z }t|S)z Generate key from call arguments :param args: call positional args :param kwargs: call keyword args :return: )rrrshash)rrNrOseedkws r6 _make_keyzTimedCache._make_key>sB   ''B E"II DDzzr5funcr`ctfd}tfd}tjr|n|}j|_|S)a  Use it to cache calls to decorated function @TimedCache(expiration=timedelta(minutes=10)) async def func(*args, **kwargs): pass :param func: decorated function :return: NOTE: is not thread safe. chK||}j|}|tjx}j|< tj|jd{VnP#tj $r=|j|urtjx}j|<n j|}YnwxYw   j |\}}ns#t$rftj jkrj d|i|d{V}|t!jfj |<YnwxYw|n#|wxYw|S)NTFlast)rCr7r;rprracquirer5r9 TimeoutErrorr<r6rrr7popitemr?release)rNrOrlockrrrDrs r6 wrapper_asyncz*TimedCache.__call__..wrapper_asyncXs..v..C;??3''D|*1,..8t{3' 00!* (E(E(G(G+ 0 0 0 t{3///29,..@t{3//#{3/ 0  0  : $ 3IFAA:::4:$,66 ***666#'4#8#8#8888888F&,dikk&9DJsOOO :   MsEABA C&%C&+FDFA-F>FFFF/cZ||} j|\}}nm#t$r`t jjkrjd|i|}|tjfj|<YnwxYw|S)NFrG)r<rCr6rrr7rKr?)rNrOrrrrDrs r6 wrapper_syncz)TimedCache.__call__..wrapper_sync{s MMOOO..v..C 6 JsO  6 6 6tz??dl22J&&E&222t.v.."($)++"5 3  6 Ms>A'B('B()rrpiscoroutinefunctionr')rrDrNrPrQs`` r6rzTimedCache.__call__Ks t       D t       *400 MM  #.r5N)r) r-r.r/rr<r'rCr#rr4r5r6r3r3)s   CQC1CCCCCCr5r3rc>K|r<|s& |n#t$rYnwxYwdS|t j|h|d{V\}}|rL|s|nd}|rt d||dSdS|s4t d||| ddSdS)uCancel *task* and wait up to *timeout* seconds for it to finish. Unlike the common ``task.cancel(); suppress(CancelledError); await task`` pattern, this function **always returns** within *timeout* seconds — even if the task catches ``CancelledError`` and continues running (see DEF-40570 / CPython #103486). Uses ``asyncio.wait`` (not ``wait_for``) because ``wait_for`` also hangs when the inner task survives cancellation. Nrz&Task %r raised during cancellation: %sz.Task %r did not finish within %ds after cancelc$t|dS)Nz*Abandoned task failed after cancel timeout)message)log_future_errors)ts r6z"safe_cancel_task..s'Gr5) done cancelledrrcancelrpryrr_radd_done_callback)taskrrYrrs r6safe_cancel_taskr^s^ yy{{~~       KKMMML$999999999GD!  &*nn&6&6@dnnD  P NNCT3 O O O O O P P YY[[  r?s r6_has_no_new_privsrvs % & & 2! 2 2??=112::<<?c111 2 2 2 2 2 2 2 22 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2      5s@A19A% A1A% A1%A))A1,A)-A11 A>=A>) systemd-runz--quietz--waitz--pipez --collectz--property=NoNewPrivileges=noz--property=ProtectSystem=noc`|sdStd|DS)Nr4c3,K|]\}}d|d|VdS)z --setenv=r4Nr4)rr@rAs r6rz+_systemd_run_setenv_args..s7==A$Q$$$$======r5)rsrenvs r6_systemd_run_setenv_argsr|s4 r ===== = ==r5rcts|Stt|zdd|fz}dd|DS)zWrap a shell command so it runs as a transient systemd unit outside the agent's NoNewPrivileges= sandbox. Returns the original command unchanged when this process is not under NNP./bin/sh-c c3>K|]}tj|VdSrshlexquoterr*s r6rz._wrap_outside_sandbox_shell..*22qEKNN222222r5)rv_SYSTEMD_RUN_BASEr|rrr{partss r6_wrap_outside_sandbox_shellrsa     "3 ' ' ( dC  ! 8822E222 2 22r5ctst|Sttt|zdzt |zS)z5Argv-form counterpart of _wrap_outside_sandbox_shell.)z--)rvrrrr|rs)argvr{s r6_wrap_outside_sandbox_argvr"sY   Dzz  "3 ' ' (   ++   r5rzcLKtt|||fi|d{VS)urun_cmd_and_log variant that escapes the agent's systemd sandbox. Use for shell commands whose RPM/apt scriptlets perform LSM domain transitions on exec (e.g. kernelcare install, hardened-php groupinstall) — see the module-level NNP note above. rzN)rsrrrnr{ros r6run_cmd_and_log_outside_sandboxr.sZ!#CS111       r5cJKtt||fi|d{VS)z7run() variant that escapes the agent's systemd sandbox.rzN)rrrr{rOs r6run_outside_sandboxr>s</#>>>II&II I I I I I IIr5cJKtt||fi|d{VS)z=check_run() variant that escapes the agent's systemd sandbox.rzN)rrrs r6check_run_outside_sandboxrCs<5dDDDOOOO O O O O O OOr5cNtsdS tjddgtjtjddj}n#t tjf$rYdSwxYwtj d|}|dSt| tkS)z=Return True iff systemd-run can host a transient unit for us.Frwz --versionT)rhritextrz\d+) r=rlrrmrrhrSubprocessErrorr$r%rr&_SYSTEMD_RUN_MIN_VERSION) version_liner*s r6_systemd_run_supportedrWs   u " K (#&       [0 1uu Ifl + +E }u u{{}}  !9 99s4AA! A!cts|Stt|zdd|fz}dd|DS)zWrap a shell command so PID 1 owns its cgroup, keeping its CPU and memory off the agent's. Returns the command unchanged where systemd-run cannot host it.r~rrc3>K|]}tj|VdSrrrs r6rz,_wrap_in_own_cgroup_shell..wrr5)rrr|rrs r6_wrap_in_own_cgroup_shellrlsa " # #  "3 ' ' ( dC  ! 8822E222 2 22r5cLKtt|||fi|d{VS)zrun_cmd_and_log variant that keeps the command's resource usage out of the agent's cgroup. Use for package transactions heavy enough to matter against the agent's own CPU and memory allowance. rzN)rsrrs r6run_cmd_and_log_in_own_cgrouprzsZ!!#3///       r5ceZdZdZdS)rc `|jdkr7|jr0|dd}|jr |j|d<|j| t j|dS#t$rt|j ddpt|j dd}t|j ddpt|j dd}t|j d dpt|j d d}|j }|r|j p|j }td ||j||YdSwxYw) NPENDINGz%Task was destroyed but it is pending!)r]rUsource_tracebackr/r-gi_codecr_codegi_framecr_framez+!> Finalizer error in {}() {} at {} line {})_state_log_destroy_pending_source_traceback_loopcall_exception_handlerr__del__AttributeErrorgetattr_coro co_filenamef_linenoco_firstlinenoprintr)rcontextrrframerlinenos r6rz Task.__del__s{ ;) # #(A #BG% E.2.D*+ J - -g 6 6 6  N4    4:~t<< JAAD4:y$777 It<<DDJ D99W J>>E'H.F43FF =DD$+x       sACD-,D-N)r-r.r/rr4r5r6rrs#r5rcfd}|S)zReturn async callback which waits for *seconds*. Usage: @retry_on(Error, on_error=await_for(seconds=PAUSE_INTERVAL), timeout=T) async def coro(): 'here's something that may raise Error.' c<Ktjd{VSr)rpr)rNrcs r6pausezawait_for..pauses)]7+++++++++r5r4)rcrs` r6rwrws#,,,,, Lr5cjtgstdfd}|S)a Retry the function call on exception (or exceptions, if given in tuple) at most *max_tries*. Await *on_error* (if set) for each exception. If *timeout* is set, stop all attempts in *timeout* seconds. If *silent* is set to True - don't raise exceptions after max If *should_retry* is set - await it and on False, stop auto-retry cycle tries or timeout. zSet any of max_tries, timeoutc tj fd}tj fd}tjr|S|S)Nc~Krtjz} stjdnt d dzD]} rg|tjz }|dkr$t j|i||d{VcS s t j r dn|i|d{VcS}#t jt j f$r$rX}  ||d{V}|s }| kr s r d|  ||d{VYd}~d}~wwxYwdS)Nrrr Timeout exceeded when calling %s0Max tries exceeded when calling %s with error %s) r?r@ itertoolscountrrprrJrrrNrOend_timerremaining_timershould_retry_retrrDrDrdre should_retrysilentrs r6rNz2retry_on..decorator..wrapper_asyncs, 6>++g5!- """1i!m,,( /( / #/;)1DN4D4D)D)A--)0)9 $d 5f 5 5~***$$$$$$$*"&-&: :!$" # $F!"!"!"&*T4%:6%:%::::::::::,g.DE ///#/1=c11E1E+E+E+E+E+E+E(/* )AI~~%! II!, $ #   +&hsA.........#//( /( /s?C 4C D:"AD55D:crtjz} stjdnt d dzD]} rH|tjz }|dkr |i|cS st  r dn |i|cSX#$rL}  ||}|s }| kr s r d|  ||Yd}~d}~wwxYwdS)Nrrrr)r?r@rrrrJrrs r6rPz1retry_on..decorator..wrapper_syncs 6>++g5!- """1i!m,,$ )$ ) ) 5)1DN4D4D)D)A--#'4#8#8#8888#)"&2 2!$" # $F!"!"!" $tT4V44444 )))#/+7<Q+?+?(/* )AI~~%! II!, $ #   + a(((#)'$ )$ )s%B.)BC, AC''C,rSrrprQ) rDrNrPrrDrdrerrrs ` r6rUzretry_on..decorators   + /+ /+ /+ /+ /+ /+ /+ /+ /+ /+ /  + /Z   ' )' )' )' )' )' )' )' )' )' )' )  ' )R  &t , ,   r5)rrk)rrerdrrrDrrUs``````` r6rurusz$  7# $ $:8999\ \ \ \ \ \ \ \ \ \ \ | r5ctjfd}tjfd}tjr|n|S)zhIf func throws an exception it is catched, converted to a string and returned as a result of a call.crK |i|d{VS#t$r}t|cYd}~Sd}~wwxYwrrreprrNrOrrDs r6rNz,stub_unexpected_error..wrapper_async5sg t.v........ .   77NNNNNN s  6166cb |i|S#t$r}t|cYd}~Sd}~wwxYwrrrs r6rPz+stub_unexpected_error..wrapper_sync<sQ 4((( (   77NNNNNN s .)..r)rDrNrPs` r6stub_unexpected_errorr1s_T _T $7== O==<Or5c2 tjfd}|S)zkA decorator that logs uncaught exceptions ignoring them otherwise. CancelledError is not handled. Nctjfd}tjfd}tjr|S|S)Nc K |i|d{VS#tj$r$r'}dtdd|Yd}~dSd}~wwxYwNzIgnoring exception from %s: %sr/r)rprrrNrOrrr log_handlers r6rNz>log_error_and_ignore..decorator..wrapper_asyncOs !T426222222222)       4D.&99 s AA  Ac t |i|S#$r'}dtdd|Yd}~dSd}~wwxYwr)rrs r6rPz=log_error_and_ignore..decorator..wrapper_sync\s tT,V,,,    4D.&99 s 727r)rrNrPrrs` r6rUz'log_error_and_ignore..decoratorNs                          &t , ,   r5)r_r)rrrUs`` r6log_error_and_ignorerFs9 l       < r5cfd}|S)z'Abort the agent service on *exception*.cLtjfd}|S)NcK |i|d{VS#$r/}t|Yd}~dSd}~wwxYwr)r_r)rNrOrabortrrs r6rQz2abort_agent_on..decorator..wrapperss !T426222222222     ###  s A$AArR)rrQrrs` r6rUz!abort_agent_on..decoratorrsC            r5r4)rrrUs`` r6abort_agent_onros*       r5cRtjdd|S)zPascalCase to snake_casez([a-z])([A-Z])z\1_\2)r$subrK)strings r6 snake_casers# 6"Hf 5 5 ; ; = ==r5g?g?cHdt|vS)Nr)rorK)rs r6_is_db_locked_errorrs s3xx~~'' ''r5)exec_expr_with_empty_iterc'K|s|rdg}nt|t}ddlm}|j5|D]}||g|REd{V ddddS#1swxYwYdS)a] Get iterator over results of sql expression expr. Given iterable will be split for chunks and we will return iterator containing results of all split queries. Useful for sql selects with in_() in order to avoid too many sql variables error. If exec_expr_with_empty_iter is True and iterable is None(empty) we will process expression once, passing here chunk=None expr(None, *args) :param expr: :param iterable: :param exec_expr_with_empty_iter: if iterable is None(empty) process given expression once, passing here chunk=None expr(None, *args) :return: Nr rinstance)rCHUNK_SIZE_SQL_QUERYdefence360agent.modelrdb transaction)exprr rrNchunksrrs r6get_results_iterable_expressionrs& L1L 6JKKK......  " "** * *EtE)D))) ) ) ) ) ) ) ) ) *******************sA##A'*A'rctt||ddlmd}t t |t dzdfd}|S) a Get number of results of sql expression expr. Given iterable will be split for chunks and we will return number of results of all split queries. Useful for sql delete with in_() in order to avoid too many sql variables error. The iterable is materialized BEFORE the database transaction opens, and the transaction is retried on transient SQLite lock errors. This matters because callers commonly pass a generator that does its own SELECTs (e.g. ``MalwareHit.delete_hits(get_outdated_entries(...))``): in SQLite WAL mode, the read snapshot taken inside a transaction becomes stale as soon as another writer commits, and the subsequent write raises SQLITE_BUSY_SNAPSHOT, which PRAGMA busy_timeout does *not* cover. rrrcttd|dz zzt}td||t |t j|dS)Nrbrz;SQLite lock contention, retrying in %.3fs (retry %d/%d): %s)minDB_LOCK_RETRY_BACKOFF_BASEDB_LOCK_RETRY_BACKOFF_MAXr_rDB_LOCK_MAX_RETRIESr?r)rattemptbackoffs r6_backoffz-execute_iterable_expression.._backoffsd &!! *< = %    I         7r5rc t|Sr)r)rrs r6rXz-execute_iterable_expression..s*=c*B*Br5)rerdrcd}j5D] }||gRz }! dddn #1swxYwY|Sr)rrexecute)rrrNrrrs r6 _execute_allz1execute_iterable_expression.._execute_alls [ $ $ & & 8 8 8 844----55777 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8s$AAA)rrrrrrurr)rr r rNrrrrs` ` @@r6execute_iterable_expressionrs$/(zBBB C CF......   %)BB     <>>r5cXtj|dddzS)Nr\nr)rfsencodergrs r6encode_filenamers% ;t||D%00 1 1E 99r5cbtj|ddddS)Nr(rr)rfsdecodergrs r6decode_filenamer s+ ;t  SbS ! ) )% 6 66r5cNtjtj|Sr)base64 b64encoderrrs r6base64_encode_filenamer s  BK-- . ..r5b64namechttjtj|Sr)rrrr  b64decode)rs r6base64_decode_filenamers%  F,W5566 7 77r5cV tj|}n#t$rd}YnwxYw|S)zS Like pwd.getpwnam(username) but returns None instead of raising KeyError. N)rrr)rrs r6rrsAh''  Ms  &&c>tt|||S)zH Put the specified `value` inside the [`low`, `high`] interval. )maxr)rlowhighs r6cliprs s5$ % %%r5Background task failedc| tj} |dS#tj$rYdSt $r}|d||Yd}~dSd}~wwxYw)a[ Callback for asyncio.Future that logs exceptions and ignores CancelledError. Use this as a done_callback for asyncio tasks/futures: future.add_done_callback(log_future_errors) Or with custom logging: future.add_done_callback( lambda f: log_future_errors(f, logger.warning, "Upload failed") ) Nz%s: %s)r_rrrprr)futrrUrs r6rVrV sn *  !     *** Hgq)))))))))*s&A A AAr.crfd}||i|}|||S)z Use this function in plugin initialization instead of loop.create_task to be able to see the exceptions from the specified coroutine. c|sA|/d||ddSdSdS)Nz1Unhandled exception during plugin initialization!)rUrr])rZrr)r]rs r6_log_exceptionz6create_task_and_log_exceptions.._log_exception+sv~~ DNN$4$4$@  ' 'L!%!1!1        $@$@r5) create_taskr\)rrrNrOrnew_tasks` r6create_task_and_log_exceptionsr "sY     d 5f 5 566H ~... Or5cfd}|S)a5 Create coroutine from regular function Useful to pass functions to APIs requiring coroutines Note: coroutine will still block event loop in main thread. For most blocking functions, run_in_executor should be considered instead :param function: :return: coroutine running function cK|i|Srr4)rNrOfunctions r6rzmake_coro..coroFsx((((r5r4)r#rs` r6 make_coror$<s#))))) Kr5cK|tkr tj}n tj}|dt |ddx}rd|dnd||t jtd{VdS)Nz7Failed to copy data%s to modsec ruleset dir %r, try: %srz ()r)COPY_TO_MODSEC_MAXTRIESr_rrrrpr_MODSEC_COPY_FAILURE_TIMEOUT)rrrDfns r6log_failed_to_copy_to_modsecr*Ps ###lnCA$S*d;;;rD R "   -4 5 5555555555r5) err_buf_sizec 4Kd}t|}tj|dtjjtjjd|d{V} tj||j||j23d{V}|WV 6 |d{V}|dkr%t||dd |dS#|d{V}|dkr%t||dd |wxYw)z Start *cmd*, yield its stdout line by line [b' '] If *cmd* return nonzero exit status, raise CheckRunError with the last *err_buf_size* lines from stderr. cJK|23d{V}||6dSr)append)pipebufr?s r6read_pipe_intoz1readlines_from_cmd_output..read_pipe_intohsL       $ JJt    $$s")maxlenT)rjrhriNrr5) rrprtrhrmrrirhryrr)rr+ror1err_bufr~r?rs r6readlines_from_cmd_outputr4^s<(((G/ !&!&         D I NN4;@@AAA+       $JJJJJ&+ 99;;&&&&&& ?? Cchhw6G6GHH H ? 99;;&&&&&& ?? Cchhw6G6GHH H H H H Hs*C:BCADrb)rddelaycKtd|dzD]3}||d{V}|s!||krtj|d{V0|cSdS)z Retry *predicate_coro(*args)* until it becomes true, but no more than *max_tries* attempts. Sleep for *delay* seconds before the next *predicate_coro()* call. Return whether the predicate became true. rN)rrpr)predicate_corordr5rNrrs r6finally_happenedr8sIM**%~t,,,,,,, 'I---&& & & & & & & &  r5'cKt|dD]-\}}|WV||zdkrtjdd{V.dS)z6Yield to the event loop every *chunk_size* iterations.r)rErN) enumeraterpr)r r ritems r6 nice_iteratorr=soXQ///##4 Nq -"" " " " " " " "##r5ceZdZdZdZdZdS)LazyLocka Descriptor object to share async Lock between client objects. Used in order to achieve lazy evaluation of the lock and share state between it's clients. Using asyncio.Lock in client code directly: >>> class Foo: >>> lock = asyncio.Lock() leads to an unclear error ([Errno 9] Bad file descriptor), when trying to move this Lock during demonization process. cd|_dSr)rr>s r6rzLazyLock.__init__s  r5cN|jstj|_|jSr)rrpr)rrowners r6__get__zLazyLock.__get__s!z ( DJzr5N)r-r.r/r]rrCr4r5r6r?r?s<  r5r?c|}|rd|vsd|vrdS|dd\}}||fS)zOParse RPM output line, return (package_name, version) or None if not installed.z not installed: Nr)rrKr )r?pkg_nameversions r6_parse_rpm_linerHs[ ::<.wrappers\ 7>>= > > +4 T4*6*********r5r)rDrQs` r6check_disabled_firewallrSs3 4[[++++[+ Nr5> imunify-ui imunify-coreimunify-antivirusimunify360-firewallpackagescKt\}}t|}t||zddd{V}t|||S)a Retrieves the version of the specified system packages using a command and regex specific to the current system. Parameters: packages (Iterable[str]): A set of package names to retrieve version for. Returns: A dictionary mapping package names to their corresponding version strings, or None if the package is not installed or version information cannot be retrieved. rF)rrN)rNrrsafe_run_with_timeout_parse_package_info_output)rXr parse_line packages_listrs r6system_packages_infor^st-..OCNNM( mR%F &fmZ H HHr5rr\cnfd|Dfd|DS)NcXi|]&}|xdxdx#'S)rrr4)rr?r\pkgrvers r6 z._parse_package_info_output..sf  j&& &F1I S  1I S  Sr5c<i|]}||Sr4)r;)rraparseds r6rcz._parse_package_info_output.. s% 5 5 5SCC 5 5 5r5) splitlines)rrXr\rerarrbs `@@@@r6r[r[sf %%''F 6 5 5 5H 5 5 55r5cK tjt|fi||d{VS#tj$r|d|YdSwxYw)Nrz#Command %s failed: Timeout occurredr)rprrrJ)rzrrDrOs r6rZrZ#s% W ' ' ' '              17;;;rrs&+A  A nc#K|dkrtdt|}tt||x}r%|Vtt||x}#dSdS)Nrzn must be at least one)rkrrrr)r rhitbatchs r6batchedrl/s  1uu1222 hBr1 && &% r1 && &%r5rOc#RKt|D]}fd|DVdS)Nc"i|] }|| Sr4r4)rr@rOs r6rcz batched_dict..=s&&&1q!A$&&&r5)rl)rOrhrks` r6 batched_dictro;sKA''&&&&&&&&&&&''r5cn tjddgd}|d}|s?t drtjddgd}d|vrd}|S#t $r&}td |Yd}~d Sd}~wwxYw) Nhostnamez-fT)r)z.cloudwaysapps.comz.cloudwaysstagingapps.comz/usr/local/sbin/apminfo Cloudwaysz$Error while checking environment: %sF) rl check_outputrrrr:rr_r)rq _is_cloudwaysrrs r6 is_cloudwaysrv@s+  T   %'' !)) ?   %&;!rs  ********////////::::::::::222222222222''''''                     ######00000000%%%%%% GCx     8 $ $ d0116d344 "d#BCC$899 SJNN:B?? +++++D+++Q     K K K K4T    4        00 3u 0000f 0      K2    (5  5    .; ' ' ' ' ' ,   ++++++++@        :?;;;;F(,  ddd 3J $ d $Jd ddddNQ ? ?C ? ? ? ?h'h'h'h'h'h'h'h'X%[4 A A A58 A A A A A #### 38_ ####2.<     $)#         5 T         5 T     % % %--------`0E6D&3#$ % % % , ,h ,C ,) , , , ,    ########"R   !!! !"2eeeeeeeeP -.! ! ! ! ! H)))ZQ4 $>>> 3 3S 3s 3 3 3 3     $      ,0JJJJJ 26PPPPP"Q:::: :( 3 33 3S 3 3 3 3 $      7<B   $   sssslPPP*$-$&&&&R%7&>>>  !((((( 6;*****@';11111h:::777//%////8E8d8888&&&****.i(4      6 6 6%(III cIIIID=>Q      ####0#%S/D"835c?T#9"Q $s)XseU38_t%;;< <= .FFFFF FFF   " IsmI #sTz/IIII, 6  63i 6#c3h$ 667 6 #sTz/ 6 6 6 6 !,           'DcN's'''' Q ( T c     t    &&&&&r5defence360agent/utils/__pycache__/__init__.cpython-311.pyc0000644000000000000000000031323500000000000020366 0ustar r_j V ddlZddlZddlZddlZddlZddlZddlZddlZddlZddl Z ddl Z ddl Z ddl Z ddl Z ddlZddlZddlZddlmZddlmZmZddlmZmZddlmZmZmZddlmZddl m!Z!ddl"m#Z#m$Z$m%Z%m&Z&dd lm'Z'dd lm(Z(dd l)m*Z*dd l+m,Z,dd l-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3m4Z4m5Z5ddl6Z6ddl7Z7ddl8Z8ddl9m:Z:ddl;mm?Z?e5de0Z@ejAeBZCdZDdZEdaFdZGe*dZHdZIe*dZJe*dZKe*dZLeMejNOddZPGdd e!ZQejRd!d"ZSedd#ZTeCddfd$ZUGd%d&ZVdejWejWd'dfd(e4eMeXeXffd)ZYddd*d+ZZGd,d-ej[Z\e\fd(eXfd.Z]e\fdd/Z^dd(e_fd1Z`d2ZaGd3d4Zbd5ZcGd6d7edZed8Zfd9Zg dd;Zhd0ddd0ddd<d=eie_zejjzd>eMdzd(eifd?ZkejRdd@Zldd(e_fdAZmGdBdCZnejodDfdEe_dFeMd(e_fdGZp ddEe_dFeMd(e4e_eMffdHZqdIZrdJZsddLZt ddNZudOZvejRdPeMfdQZwejxe6jydRd0SZzdTZ{dEejjdUeXd(dfdVZ|dWZ}dEejjdXeXd(eifdYZ~dZZGd[d\ZeEfd]Zd^Zed_eMfd`Zddbe_d(e*fdcZdde_dbe_d(eifdeZdfZddhedieMd(efdjZdkZGdldmeZejRd:!dnZdoZdd(eifdpZGdqdrZeZdsdtduZdvZdwZejRd!d(eifdxZdyZdzZdd{e_d(e_fd|Zdd}Zdd~dZdd~dZdd~dZdZejRd!d(eifdZdd{e_d(e_fdZdd~dZGddejZdZ ddZdZeddfdZefdZdZdZdsZdZdZd(eifdZd'ddZeddZdZdZdde*d(eXfdZdeXd(e*fdZdZdZddZde0de/ffdZdZdsZdsZdZdRdd{e3e_fdZddsddZddZGddZdXe_d(ee_e_fdzfdZdXe_d(ee_e_fdzfdZejRd!d(eee_e0e_gee_e_fdzfffdZGddedZdZehdZdee_d(ee_e_dzffdZde_dee_de0e_gee_e_fdzfd(ee_e_dzffdZeCjfd(e_fdZdeMfdZde1e.e.fdeMfdZejRd!dZde*d(eMfdZde*fdZd„ZdS)N)Future) OrderedDictdeque) GeneratorIterable) ExitStackcontextmanagersuppress) timedelta)Enum)LOCK_EXLOCK_NBLOCK_UNflockwraps)islice)Path)NamedTemporaryFile)Any AwaitableCallableDict FrozenSetListTupleTypeVar)OperationalError)is_safe_subdir_namermtree)atomic_rewrite_fdF)bounduser_id)z User-AgentzAccept-LanguagezAccept-Encoding ConnectionDNTz.i360bakz/run/systemd/systemz/etc/cloudlinux-edition-soloz/var/run/imunify-antivirus.pidz/var/run/imunify360-agent.pidz/var/run/imunify360.pid%IMUNIFY360_HTTP_REQUEST_RETRY_TIMEOUT<ceZdZdZdZdZdZdS)ScopezAV onlyz AV and IM360z IM360 onlyzIM360 resident onlyN)__name__ __module__ __qualname__AVAV_IM360IM360IM360_RESIDENTS/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/__init__.pyr,r,Hs" BH E*NNNr5r,)maxsizecto2tot S)zReturn True if /run/systemd/system folder exists: [sd_booted] (https://www.freedesktop.org/software/systemd/man/sd_booted.html) )_SYSTEMD_BOOTED_DIRexistsis_dir is_symlinkr4r5r6is_systemd_bootr=OsC ""$$ 1  & & ( ( 1#..00 0r5c#K|s|sJtj}|p|jd|dVtj}|p|jd|||z dS)z :param str: action name to log :param logging.Logger: logger you want action name and timing to be logged with :param func: log function to use (`log` has preference over `logger_`) z %s startedNz%s took %.2f second(s))time monotonicdebug)actionlogger_logstartstops r6timeitrG\sz c> N  ESGM<000 EEE >  DSGM3VTE\JJJJJr5cfd}|S)NcNtjfd}|S)NcKtpj5|i|d{VcdddS#1swxYwYdSN)rCrDrGr-argskwargsrBfunrDrCs r6wrapperz+timefun..decorator..wrapperns.#,SIII 2 2 S$1&11111111 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2s 8<< functoolsrrPrQrBrDrCs` r6 decoratorztimefun..decoratormsH    2 2 2 2 2 2 2   2r5r4rCrBrDrUs``` r6timefunrWls0 r5c0eZdZdZeeddfdZdS)synczF the same timefun decorator variation but without async/await Ncfd}|S)z :param logging.Logger: logger you want action name and timing to be logged with :param str: action name to log cNtjfd}|S)Ncztpj5|i|cdddS#1swxYwYdSrKrLrMs r6rQz0sync.timefun..decorator..wrappersF2clGMMM003///000000000000000000s 044rRrTs` r6rUzsync.timefun..decoratorsH _S ! ! 0 0 0 0 0 0 0" ! 0Nr5r4rVs``` r6rWz sync.timefun}s0       r5)r-r.r/__doc__ staticmethodloggerrWr4r5r6rYrYxsEt\r5rYFreturnc $K||tdtj}|r't|tsJ|g}t j}n*t|ttfsJt j }ttdtd|||||dd|d{V}| |d{V\} } |d{V} td |||| | | f| | | fS) zYAsynchronous command executor. Returns a tuple (exit_code, stdout_data, stderr_data).Nz/stdin and input arguments may not both be used.r)seconds) max_trieson_errorTstdinstdoutstderrstart_new_sessionz run(%s, stdin=%s, shell=%s) = %s) ValueError _subprocessPIPE isinstancestrasynciocreate_subprocess_shelllisttuplecreate_subprocess_execretry_onBlockingIOError await_for communicatewaitr_rA) commandrgrhrishellinputrOcreate_subprocessprocouterr exit_codes r6runrs   NOO O  ;'3''''')#;'D%=11111#:1y/C/C/C             D%%e,,,,,,,,HCiikk!!!!!!I LL*  C  c3 r5)looptimeoutc|rtdD]b} tj}|sn7n#t$rYnwxYwtjtjc|tjt|tj r|ntj ||S)zjRun coroutine from a blocking code (outside the event loop). Coroutine will be wrapped in Task. Nrbr) rangerpget_event_loop is_closed RuntimeErrorset_event_loopnew_event_looprun_until_completewait_forrnrTask)cororr_s r6run_corors  |q = =A -//~~''E       "7#9#;#; < < < <  " "tW^44 LDD',t:L:L     s? A  A ceZdZdZdS) CheckRunErrorcd}||j|j|jpd|jpdS)Nz[Command {cmd!r} returned non-zero code {returncode}, Stdout: {output}, Stderr: {error} )cmd returncodeoutputerror)formatrrrdecoderi)self_MESSAGEs r6__str__zCheckRunError.__str__s_ $  ;%%''/4+$$&&.$    r5N)r-r.r/rr4r5r6rrs#      r5rc`Kt|fi|d{V\}}}|dkr||||||S)zJ Asynchronous command executor. Returns output as bytestring. Nr)r)rz raise_excrOrrrs r6 check_runrsZ "%W!7!7!7!7777777JSQi GS#666 Jr5cKt|tjtjtjd{V\}}}|dkr |||dS)z Asynchronous command executor. Raises raise_exc if exit code is nonzero. Stdin, stdout and stderr of command are connected to /dev/null. )rgrhriNr)rrlDEVNULL)rzrcoders r6check_exit_codersy !"" JD!Q qyyig&&&yr5TcK t|fi|d{V\}}}n,#t$rtd|YdSwxYw|r%|dkrtd|||dS |}n,#t $rtd|YdSwxYw|S)zGSafe run command. Returns stdout as string or empty string on errorNzCommand %s failed with OSErrorrz'Command %s failed with exit code %s: %sz#Command %s returned non-utf8 output)rOSErrorr_warningstriprUnicodeDecodeError)rzcheck_returncoderOrcrrresults r6safe_runrs 33F33333333 C 7AAArrB!GG 5wC   r##%% .wrapper!s  %+--Kr5r4)rrQrs` @r6plainold_lazy_initrs.K Nr5ceZdZdZdZdZdS) PeriodicCheckz Invoke a callback with a certain period and return cached result in between. Raising an exception from the callback does not affect the next check schedule. c||_||_tj|z |_d|_t tj|_ dSr) _cb_coro_check_every_n_secondsr?r@_last_check_timestamp_last_check_resultrrpLock_lock)rcb_corocheck_every_n_secondss r6__init__zPeriodicCheck.__init__3sD &;#%)^%5%58M%M""&' 55 r5cK|4d{Vtj|jz }||jkrVt d|j|jtj|_|j|i|d{V|_|jcdddd{VS#1d{VswxYwYdS)Nz3Timeout %d seconds has expired, doing the check: %s) rr?r@rrr_rArr)rrNrOdeltas r6__call__zPeriodicCheck.__call__<s\::<< + + + + + + + +N$$t'AAE333 I/M .2^-=-=*0= t0Nv0N0N*N*N*N*N*N*N'* + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +sBB33 B=B=N)r-r.r/r]rrr4r5r6rr*s<666 + + + + +r5rcfd}|S)Nc$t|Sr)r)rnsecs r6decoratezcache_result..decorateKsT4(((r5r4)rrs` r6 cache_resultrJs#))))) Or5ceZdZdZdS)RecurringCheckStopz: raised by coroutine to stop recurring_check loop Nr-r.r/r]r4r5r6rrQs Dr5rcK t|r!tj|di|d{Vntj|d{VdS#tj$rYdSwxYw)NFTr4)callablerpsleepCancelledError)period period_kwargss r6wait_for_periodrYs F   (- 7 7 7 788 8 8 8 8 8 8 8 8-'' ' ' ' ' ' ' 'u  !ttsA AA#"A#c8K|r|rt|fi|d{VndSNF)r)checkrrs r6should_stop_after_period_passedrdsG   of66 666666666 r5 c fd}|S)z run decorated corotine in a loop every :period: seconds. If more then consecutive_err_limit error occured, exit loop. :param period: :param consecutive_err_limit: :param check_period_first: default false :return: cFtfd}|S)NcKd} tfid{VrdS |i|d{Vd}n#t$rOd|vrG t|dtr|dn#t $rYnwxYwYdSt j$rYdSt$r}|dz }|kr t dYd}~dSt|tj r3t d|j |j|j|jnt dYd}~nd}~wwxYwt fid{VrdST)NrT lock_filerz-Error count exceeded limit,exiting check loopz+Failed to run %s (%s). stdout=%s, stderr=%szError executing %s)rrrnrunlinkFileNotFoundErrorrpr Exceptionr_ exceptionrlCalledProcessErrorrrrri) rNrOconsecutive_err_cntexccheck_period_firstconsecutive_err_limitrPrrs r6wrappedz3recurring_check..decorator..wrappedysH"# ' 8&2?E,#t.v.........:+,''9*"f,,!)&*=tDD= &{ 3 : : < < <0!!! D!EE-EE DDD'1,'*-BBB((K!#{'EFF D((IGNJJ (()=sCCC!D&9**F6CEO' sK0D?5A54D?5 B?D?BD?D? D?"%D: A(D::D?r)rPrrrrrs` r6rUz"recurring_check..decoratorxsI s) ) ) ) ) ) ) )  ) Vr5r4)rrrrrUs```` r6recurring_checkrls7--------^ r5)backupuidgidallow_empty_content permissionsdir_fdrrc t|tr|}|'|rtdt |||||||St t 5t|d5}|t|dz} dddn #1swxYwY| |kr ddddS dddn #1swxYwY|s |st d||dS|rt|ttj fr|} ntj|tz} t t 5t!j|| dddn #1swxYwY|k t%jtj|j}n>#t $r1tjd} tj| d | z}YnwxYwtj|\} } t1| st d | t55}t7d | d | d zdd5fd}||||*|(tj ||tj! |tj" dddn #1swxYwYtj#j$||%dddn #1swxYwYdS)aAtomically rewrites *filename* with given *data*. If *filename*'s content is *data* already, do nothing. If both *uid* and *gid* are given then resulting file is chowned to given user id and group id. Skip rewrite with empty content if *allow_empty_content* is False. Chmod to given access *permissions* else preserve *filename* 's permissions. Return True if *filename* file was updated, False otherwise When *dir_fd* is provided it must be an O_NOFOLLOW-opened file descriptor for the parent directory of *filename*. All file I/O is then performed relative to that descriptor, closing the TOCTOU symlink-attack window. *backup* is not supported with *dir_fd*. Nz/backup is not supported when dir_fd is provided)rrrrrrbrFzempty content: %r for file: %srizParent dir is missing: wbz .i360editr)modedirsuffixprefix bufferingdeletectt5tjjddddS#1swxYwYdSr)r rosremovename)tfsr6cleanupzatomic_rewrite..cleanups/00''Ibg&&&''''''''''''''''''s=AAT)&rnroencoderkr"r ropenreadlenr_rrPathLikefspathBACKUP_EXTENSIONshutilcopystatS_IMODEst_modeumaskpathsplitrr:rrcallbackwriteflushchownfilenochmodfsyncrenamerpop_all)filenamedatarrrrrrfile old_contentbackup_filename current_umaskdirpathbasenamestackrrs @r6atomic_rewriters6${{}}   PNOO O   3#     # $ $ (D ! ! 3T))CIIM22K 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 $     t 5tXFFFu 3 fsBK0 1 1 E$OO i114DDO ' ( ( 3 3 K/ 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1,rwx'8'8'@AAKK  1 1 1HQKKM H] # # #=.0KKK  1  h//GX ==   ! !G E' E EFFF  c>     " ' ' ' ' ' NN7 # # # HHTNNN HHJJJ3?c3/// HRYY[[+ . . . HRYY[[ ! ! !- " " " " " " " " " " " " " " ". "'8$$$ 38 4s(C9&B+ C+B/ /C2B/ 3 CCCE<<FF +F668G10G1M>0B=L9- M>9L= =M>L= 1M>>NNc tdS#t$rYdSwxYw)Nz/etc/system-release)r read_textrstriprr4r5r6os_release_and_versionr"sP)**4466==??? tts25 AAc|p t}|r-tjd|}|r|dSntt d|z)z3Return os version, if can't get it raise ValueErrorz\s*(\d+\.\d+\S*)(\s|$)rz!Can't discover os version from %r)r"researchgroup cache_clearrk)release_and_versionrvmatchs r6 os_versionr+st  8 6 8 8B - 3R88  ";;q>> ! " **,,, 82= > >>r5ceZdZdZedZedZedZdZe dZ e de e e ffdZe dee fd Ze de fd Ze de fd Ze d Ze d Ze dZe dZe dZe dZe dZe dZe dZdS) OsReleaseInfoz/etc/os-release)debian)rhelfedoracentos)unknownNct|j5}|D]U} |d\}}|d||<F#t $rYRwxYw dddn #1swxYwYd|vr,t |d|d<dSt |ddf|d<dS)N="ID_LIKEIDlinux)rETC_OS_RELEASEr!r rrk frozensetget)clsdict_flinekvs r6dict_from_filezOsReleaseInfo.dict_from_file.s3 #$ % %   ;;==..s33DAq wws||E!HH!D                    (y)9)?)?)A)ABBE)    )%))D'*B*B)DEEE)   s5A;AAA; A+(A;*A++A;;A?A?r`c\|jt}tj|jr||ntj}|r|dr|d d}|dkr d|dvrd}||d<d |d|d|d|d <|d vr |j |d <n.|d vr |j |d <n|j|d <nd |d<|j|d <d |d <||_|jS)NrredzRed Hat Enterprise Linuxr/r7z {} {} ({})rrb PRETTY_NAME) cloudlinuxr1r/r6)ubuntur.r2)r=dictrr r:r9rBdistrolinux_distributionlowerr rRHEL_FEDORA_CENTOSDEBIANUNKNOWN)r<r=dosids r6to_dictzOsReleaseInfo.to_dict=sQ 9 $(FFEw~~c011 5""5))))-//515Q4::<<--//2Du}})Cqt)K)K%"&E$K+7+>+>!adAaD,,E-(???+.+Ai((!555+.:i((+.;i(("+E$K'*{E)$+4E-(CIyr5c6|dS)Nr6rQr<s r6id_likezOsReleaseInfo.id_like\s{{}}Y''r5c6|dS)NrErSrTs r6 pretty_namezOsReleaseInfo.pretty_name`s{{}}]++r5cR|ddS)zi :return: OS name, like centos, ubuntu, debian, cloudlinux, redhat in lower case r7r2)rQr;rTs r6get_oszOsReleaseInfo.get_osds" {{}}  y111r5c2|dkS)Nr/rYrTs r6is_rhelzOsReleaseInfo.is_rhellszz||v%%r5c2|dkS)Nr1r[rTs r6 is_centoszOsReleaseInfo.is_centospzz||x''r5c2|dkS)NrGr[rTs r6 is_ubuntuzOsReleaseInfo.is_ubuntutr_r5c.|dvS)N)rFcloudlinuxserverr[rTs r6 is_cloudlinuxzOsReleaseInfo.is_cloudlinuxxszz||AAAr5cJtjtSr)rr r:_CL_SOLO_EDITION_FILErTs r6is_cloudlinux_soloz OsReleaseInfo.is_cloudlinux_solo|sw~~3444r5c2|dkS)Nr.r[rTs r6 is_debianzOsReleaseInfo.is_debianr_r5c2|dkS)Nolr[rTs r6is_oracle_linuxzOsReleaseInfo.is_oracle_linuxszz||t##r5c2|dkS)N almalinuxr[rTs r6 is_almalinuxzOsReleaseInfo.is_almalinuxszz||{**r5c2|dkS)Nrockyr[rTs r6 is_rockylinuxzOsReleaseInfo.is_rockylinuxszz||w&&r5)r-r.r/r9r:rMrLrNr= classmethodrBrrorrQrrUrWrYr\r^rardrgrirlrorrr4r5r6r-r-&s&N Y{ # #F"#?@@i %%G E F F[ FS#X[<( #((([(,C,,,[,2s222[2&&[&(([((([(BB[B55[5(([($$[$++[+''['''r5r-r chunksizec0t|||dS)zReturn hash of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. r)file_hash_and_size)r hash_funcrus r6 file_hashrys h 9 = =a @@r5c|}d}t|d5} ||}|sn(|||t|z }@ dddn #1swxYwY||fS)aCalculate hash and size of the file `filename`, reading it in chunks. * filename is a path to a file; * hash_func is a function that returns hash object (one of hashlib.md5 etc); * chunksize is a size of chunks to read, in bytes. Return tuple(hash, file size).rrTN)rrupdater hexdigest)rrxruhash_sizer>chunks r6rwrws IKKE D h   FF9%%E  LL    CJJ D    ??  d ""sAA,,A03A0c|\}}||kr&tdjdit|S)z0Given login.defs line, return *varname*'s value.z"Expected {varname!r}, got {name!r}r4)r rkrvars)varname defs_linervalues r6_parse_name_valuersJ//##KD%$D=DNNtvvNNOOO Lr5cHtdkrt\a}tS)Nr()_MIN_UID_get_max_min_uid)rs r6 get_min_uidrs2~~&(( ! Or5/etc/login.defschd\}} t|5}|D]f}|drttd|}|drttd|}g dddn #1swxYwYn#tt f$rYnwxYw||fS)zGet UID_MIN, UID_MAX from the login.defs file specified as *path*. On error, return default for the current OS values. )ii`UID_MINUID_MAXN)r startswithintrrrk)r uid_minuid_maxrr?s r6rrs( #GW  $ZZ F4 F F??9--F!"3It"D"DEEG??9--F!"3It"D"DEEG  F F F F F F F F F F F F F F F F Z       G s5BA*B  B BBBBB-,B-zimunify360-captchazimunify360-webshieldclt\fdtjDS)z~ :param excludes: users to exclude in results :return: list: list of pwd.struct_passwd objects representing users cPg|]"}|jcxkrknn |jv |#Sr4pw_uidpw_name).0entryexcludesrrs r6 z(get_non_system_users..sS     el - - - -g - - - - -%-x2O2O 2O2O2Or5rpwdgetpwall)rrrs`@@r6get_non_system_usersrsR())GW      \^^   r5cdt\}fdtjDS)z; :return: list: list of str with system user names c4g|]}|jk |jSr4r)rrrs r6rz)get_system_user_names..s.   W 5L5L 5L5L5Lr5r)rrs @r6get_system_user_namesrsE"##JGQ    #&<>>   r5rc0t\}}||kSr)r)rrrs r6is_system_userrs'))GW =r5d)r7typedct|d5}|dd}|dkrF||dz |ddkr|d|||ds|dddddS#1swxYwYdS)Nzr+rrbr rseekrr endswithrrr> last_char_poss r6append_with_newliners h   q! A   FF=1$ % % %vvayyD     }}T""  GGDMMM                  B"CCCrct|d5}|dd}|dkrF||dz |ddkr|d|||ds|dddddS#1swxYwYdS)z>Append *data* to *filename* making sure there is at the end.zr+brrbr Nrrs r6append_with_newline_bytesrs h   !q! A   FF=1$ % % %vvayyE!!  }}U##  GGENNN                  rcd}t|d5}tfd|Dsd}dddn #1swxYwY|rt||S)zAdd *line* to *filename* if it is not present in the file Returns: True if the file was changed, False otherwise. Frc3HK|]}|kVdSrrr_liner?s r6 z&ensure_line_in_file..)088U5;;==D(888888r5TN)ranyrrr?changedr>s ` r6ensure_line_in_filer!s G h  8888a88888 G,Hd+++ N>AAr?cd}t|d5}tfd|Dsd}dddn #1swxYwY|rt||S)zAdd *line* to *filename* if it is not present in the file. Returns: True if the file was changed, False otherwise. Frc3HK|]}|kVdSrrrs r6rz,ensure_line_in_file_bytes..8rr5TN)rrrrs ` r6ensure_line_in_file_bytesr0s G h  8888a88888 G2!(D111 Nrctj|}t|d5}t d|d5}|D]/}||kr||0tj|j|dddn #1swxYwYddddS#1swxYwYdS)NrwF)rrr) rr dirnamerrrr rr)rr?basedirsfrrs r6remove_line_from_filer?s5gooh''G Xs%!???%CE  E{{}}$$ "'8$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%s6B4A B B4B B4#B $B44B8;B8c,eZdZdZdZefdZdZdZdS)FileLockz` Simple context manager to enable UNIX-specific file locking with flock system call rcZ||_d|_t|d|_||_dS)NFr)r lockedrrr)rr rs r6rzFileLock.__init__Ss*  sOO  r5cKtj} t|jttzd|_|S#t tf$r}|jtj kr|j tj|z kr&t d|j Yd}~dStjdd{VYd}~nd}~wwxYw)NTz)Failed to lock file %s. Timeout exceeded.r)r?rrr rrrIOErrorerrnoEAGAINrr_rr rpr)rrEexs r6 __aenter__zFileLock.__aenter__Ys  ' 'di7!2333"  W% ' ' '8u|++\DIKK%$777NNCTYEEEEEmA&&&&&&&&&&&&&& ' 's*ACAC/CCcK|jrt|jtd|_|jdSr)rrrrclose)rexc_typeexc_valexc_tbs r6 __aexit__zFileLock.__aexit__qsC ; & $)W % % %  r5N)r-r.r/r]_TIMEOUTrrrr4r5r6rrKsZ H%- '''0r5rc |g}|fdt|Dtj}|d|dd|S#ttf$r%}t d|Yd}~nd}~wwxYwdS)Nc3DK|]\}}|v t|VdSr)ro)rfieldrfieldss r6rz user_identity..s?  u JJ  r5rutf8surrogateescapez9Generation of user identity hash failed, invalid data: %s) extendsorteditemshashlibsha1r{joinrr|rkUnicodeEncodeErrorr_r) attackers_ipsourceruid_datahash_alges ` r6 user_identityr{s  !>    &v||~~ 6 6      <>>))009JKKLLL!!### * +    G         4sB%B))C:CCc0tjdkSNr)rgetuidr4r5r6 is_root_userrs 9;;! r5maskc#Ktj|} dVtj|dS#tj|wxYwr)rr )r current_masks r6run_with_umaskrsM8D>>L  s 2Arusernamec~t|tstd|ztj|vrtd t j|}n0#t$r#td|wxYwtj |j |}t|S)z Returns user's home dir if `relpath` is not specified. Otherwise, returns absolute path of `relpath` build from `username`'s home dir :raise ValueError: when user home dir is not exists z#Invalid type for %s, should be str!zInvalid usernamezUser {!r} doesn't exist) rnrorkrseprgetpwnamKeyErrorrr rpw_dirr)rrelpathpwabs_paths r6get_abspath_from_user_dirrs h $ $K>IJJJ v+,,,E \( # # EEE299(CCDDDEw||BIw//H >>s A-Br cd} t|}t||d}n>#t$r1}tt |Yd}~nd}~wwxYw|S)NFT)rr relative_torkr_rro)r rstatus user_homers r6does_path_belong_to_userrs F-h77  T y))) s1vv Ms38 A3'A..A3ctj|std|z tj|rg t jtj|jj S#t$r)ttj|jcYSwxYwtj |})NzPath %s should be absolute!) rr abspathrkr:rgetpwuidrst_uidrrrorr s r6get_path_ownerr s 7??4 ?6=>>>% 7>>$   1 1|BGDMM$899AA 1 1 1274==/00000 1wt$$ %s/B0B65B6iterable chunk_sizec#Kt|}tt||}|r%|Vtt||}|#dSdS)a Generator that splits iterable on N-parts by chunk_size items in each chunk >>> list(split_for_chunk([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], chunk_size=2)) [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]] :param iterable: :param int chunk_size: :return: generator: N)iterrrr)r r ipieces r6split_for_chunkrsp XA :&& ' 'E , VAz**++ ,,,,,r5ct|tr+td|DSt|trt d|DS|S)Nc3>K|]\}}|t|fVdSrfreeze)rkeyrs r6rzfreeze..s1JJ*#u#ve}}-JJJJJJr5c34K|]}t|VdSrr)rrs r6rzfreeze..s(22uVE]]222222r5)rnrHr:rrrrs)rOs r6rrsm!T3JJ JJJJJJ At  322222222 Hr5c&eZdZdZiZfdZxZS) Singletonzc Metaclass for creating only one instance of class, when providing the same arguments. c|t|t|f}|j|s(tt|j|i||j|<|j|Sr)r _instancesr;superrr)r<rNrOr __class__s r6rzSingleton.__call__srF4LL&..1~!!#&& "@% 3"7"7"@###CN3 ~c""r5)r-r.r/r]rr __classcell__)rs@r6rrsI J#########r5rctjdd5}|cdddS#1swxYwYdS)z3 :return str: server's external IP address zhttps://api.ipify.orgrbrN)urllibrequesturlopenrr)rs r6get_external_ipr#s    7  C C!qvvxx  !!!!!!!!!!!!!!!!!!s&AAAc<d}|||}tj|st d|d|t |d5}|}dddn #1swxYwY|S)z Reads parameter of kernel module from /sys/module/{module_name}/parameters/{parameter} :return str: value of the parameter z(/sys/module/{mod}/parameters/{parameter})mod parameterzCannot find parameter z for module rN)rrr r:rkrrr) module_namer& _MOD_PAR_PATH param_fileprs r6get_kernel_module_parameterr+s ?M%%+%KKJ 7>>* % % j8A ;; O    j#  !!  !!!!!!!!!!!!!!! Ls'BBBcd}|D][\}}t|tr%||vs|s|||<d}(t|||}?||vs|sJ|d||||<d}\|S)zPerforms deep update of dict dst with values from src. Does not overwrite subdicts in dst blindly with new dicts in src, but does a deep update of (sub)dict content recursivelyFTz already exists in )rrnrHdict_deep_update)dstsrcallow_overwriteupdatedr@rAs r6r-r-s G  1 a   ||1|A*3q6155  ---- /CFGG Nr5c8eZdZd dZdZdZdZdedefdZd S) TimedCacherct|tsJ||_||_t |_i|_dSr)rnr expirationr7rcache_locks)rr5r7s r6rzTimedCache.__init__*s<*i00000$  ]]  r5ct}|jD]J}|j|\}}tj|z |jkr||f||<K||_dS)zClear cache from expired valuesN)rr6r?r5 total_seconds)r tmp_cacherradded_ats r6_collectzTimedCache._collect1shMM : 1 1C"joOE8 h&$/*G*G*I*III!& # r5c:t|_i|_dSr)rr6r7rs r6r'zTimedCache.cache_clear:s ]]  r5c|}|r3t|}|t|z }t|S)z Generate key from call arguments :param args: call positional args :param kwargs: call keyword args :return: )rrrshash)rrNrOseedkws r6 _make_keyzTimedCache._make_key>sB   ''B E"II DDzzr5funcr`ctfd}tfd}tjr|n|}j|_|S)a  Use it to cache calls to decorated function @TimedCache(expiration=timedelta(minutes=10)) async def func(*args, **kwargs): pass :param func: decorated function :return: NOTE: is not thread safe. chK||}j|}|tjx}j|< tj|jd{VnP#tj $r=|j|urtjx}j|<n j|}YnwxYw   j |\}}ns#t$rftj jkrj d|i|d{V}|t!jfj |<YnwxYw|n#|wxYw|S)NTFlast)rCr7r;rprracquirer5r9 TimeoutErrorr<r6rrr7popitemr?release)rNrOrlockrrrDrs r6 wrapper_asyncz*TimedCache.__call__..wrapper_asyncXs..v..C;??3''D|*1,..8t{3' 00!* (E(E(G(G+ 0 0 0 t{3///29,..@t{3//#{3/ 0  0  : $ 3IFAA:::4:$,66 ***666#'4#8#8#8888888F&,dikk&9DJsOOO :   MsEABA C&%C&+FDFA-F>FFFF/cZ||} j|\}}nm#t$r`t jjkrjd|i|}|tjfj|<YnwxYw|S)NFrG)r<rCr6rrr7rKr?)rNrOrrrrDrs r6 wrapper_syncz)TimedCache.__call__..wrapper_sync{s MMOOO..v..C 6 JsO  6 6 6tz??dl22J&&E&222t.v.."($)++"5 3  6 Ms>A'B('B()rrpiscoroutinefunctionr')rrDrNrPrQs`` r6rzTimedCache.__call__Ks t       D t       *400 MM  #.r5N)r) r-r.r/rr<r'rCr#rr4r5r6r3r3)s   CQC1CCCCCCr5r3rc>K|r<|s& |n#t$rYnwxYwdS|t j|h|d{V\}}|rL|s|nd}|rt d||dSdS|s4t d||| ddSdS)uCancel *task* and wait up to *timeout* seconds for it to finish. Unlike the common ``task.cancel(); suppress(CancelledError); await task`` pattern, this function **always returns** within *timeout* seconds — even if the task catches ``CancelledError`` and continues running (see DEF-40570 / CPython #103486). Uses ``asyncio.wait`` (not ``wait_for``) because ``wait_for`` also hangs when the inner task survives cancellation. Nrz&Task %r raised during cancellation: %sz.Task %r did not finish within %ds after cancelc$t|dS)Nz*Abandoned task failed after cancel timeout)message)log_future_errors)ts r6z"safe_cancel_task..s'Gr5) done cancelledrrcancelrpryrr_radd_done_callback)taskrrYrrs r6safe_cancel_taskr^s^ yy{{~~       KKMMML$999999999GD!  &*nn&6&6@dnnD  P NNCT3 O O O O O P P YY[[  r?s r6_has_no_new_privsrvs % & & 2! 2 2??=112::<<?c111 2 2 2 2 2 2 2 22 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2      5s@A19A% A1A% A1%A))A1,A)-A11 A>=A>) systemd-runz--quietz--waitz--pipez --collectz--property=NoNewPrivileges=noz--property=ProtectSystem=noc`|sdStd|DS)Nr4c3,K|]\}}d|d|VdS)z --setenv=r4Nr4)rr@rAs r6rz+_systemd_run_setenv_args..s7==A$Q$$$$======r5)rsrenvs r6_systemd_run_setenv_argsr|s4 r ===== = ==r5rcts|Stt|zdd|fz}dd|DS)zWrap a shell command so it runs as a transient systemd unit outside the agent's NoNewPrivileges= sandbox. Returns the original command unchanged when this process is not under NNP./bin/sh-c c3>K|]}tj|VdSrshlexquoterr*s r6rz._wrap_outside_sandbox_shell..*22qEKNN222222r5)rv_SYSTEMD_RUN_BASEr|rrr{partss r6_wrap_outside_sandbox_shellrsa     "3 ' ' ( dC  ! 8822E222 2 22r5ctst|Sttt|zdzt |zS)z5Argv-form counterpart of _wrap_outside_sandbox_shell.)z--)rvrrrr|rs)argvr{s r6_wrap_outside_sandbox_argvr"sY   Dzz  "3 ' ' (   ++   r5rzcLKtt|||fi|d{VS)urun_cmd_and_log variant that escapes the agent's systemd sandbox. Use for shell commands whose RPM/apt scriptlets perform LSM domain transitions on exec (e.g. kernelcare install, hardened-php groupinstall) — see the module-level NNP note above. rzN)rsrrrnr{ros r6run_cmd_and_log_outside_sandboxr.sZ!#CS111       r5cJKtt||fi|d{VS)z7run() variant that escapes the agent's systemd sandbox.rzN)rrrr{rOs r6run_outside_sandboxr>s</#>>>II&II I I I I I IIr5cJKtt||fi|d{VS)z=check_run() variant that escapes the agent's systemd sandbox.rzN)rrrs r6check_run_outside_sandboxrCs<5dDDDOOOO O O O O O OOr5cNtsdS tjddgtjtjddj}n#t tjf$rYdSwxYwtj d|}|dSt| tkS)z=Return True iff systemd-run can host a transient unit for us.Frwz --versionT)rhritextrz\d+) r=rlrrmrrhrSubprocessErrorr$r%rr&_SYSTEMD_RUN_MIN_VERSION) version_liner*s r6_systemd_run_supportedrWs   u " K (#&       [0 1uu Ifl + +E }u u{{}}  !9 99s4AA! A!cts|Stt|zdd|fz}dd|DS)zWrap a shell command so PID 1 owns its cgroup, keeping its CPU and memory off the agent's. Returns the command unchanged where systemd-run cannot host it.r~rrc3>K|]}tj|VdSrrrs r6rz,_wrap_in_own_cgroup_shell..wrr5)rrr|rrs r6_wrap_in_own_cgroup_shellrlsa " # #  "3 ' ' ( dC  ! 8822E222 2 22r5cLKtt|||fi|d{VS)zrun_cmd_and_log variant that keeps the command's resource usage out of the agent's cgroup. Use for package transactions heavy enough to matter against the agent's own CPU and memory allowance. rzN)rsrrs r6run_cmd_and_log_in_own_cgrouprzsZ!!#3///       r5ceZdZdZdS)rc `|jdkr7|jr0|dd}|jr |j|d<|j| t j|dS#t$rt|j ddpt|j dd}t|j ddpt|j dd}t|j d dpt|j d d}|j }|r|j p|j }td ||j||YdSwxYw) NPENDINGz%Task was destroyed but it is pending!)r]rUsource_tracebackr/r-gi_codecr_codegi_framecr_framez+!> Finalizer error in {}() {} at {} line {})_state_log_destroy_pending_source_traceback_loopcall_exception_handlerr__del__AttributeErrorgetattr_coro co_filenamef_linenoco_firstlinenoprintr)rcontextrrframerlinenos r6rz Task.__del__s{ ;) # #(A #BG% E.2.D*+ J - -g 6 6 6  N4    4:~t<< JAAD4:y$777 It<<DDJ D99W J>>E'H.F43FF =DD$+x       sACD-,D-N)r-r.r/rr4r5r6rrs#r5rcfd}|S)zReturn async callback which waits for *seconds*. Usage: @retry_on(Error, on_error=await_for(seconds=PAUSE_INTERVAL), timeout=T) async def coro(): 'here's something that may raise Error.' c<Ktjd{VSr)rpr)rNrcs r6pausezawait_for..pauses)]7+++++++++r5r4)rcrs` r6rwrws#,,,,, Lr5cjtgstdfd}|S)a Retry the function call on exception (or exceptions, if given in tuple) at most *max_tries*. Await *on_error* (if set) for each exception. If *timeout* is set, stop all attempts in *timeout* seconds. If *silent* is set to True - don't raise exceptions after max If *should_retry* is set - await it and on False, stop auto-retry cycle tries or timeout. zSet any of max_tries, timeoutc tj fd}tj fd}tjr|S|S)Nc~Krtjz} stjdnt d dzD]} rg|tjz }|dkr$t j|i||d{VcS s t j r dn|i|d{VcS}#t jt j f$r$rX}  ||d{V}|s }| kr s r d|  ||d{VYd}~d}~wwxYwdS)Nrrr Timeout exceeded when calling %s0Max tries exceeded when calling %s with error %s) r?r@ itertoolscountrrprrJrrrNrOend_timerremaining_timershould_retry_retrrDrDrdre should_retrysilentrs r6rNz2retry_on..decorator..wrapper_asyncs, 6>++g5!- """1i!m,,( /( / #/;)1DN4D4D)D)A--)0)9 $d 5f 5 5~***$$$$$$$*"&-&: :!$" # $F!"!"!"&*T4%:6%:%::::::::::,g.DE ///#/1=c11E1E+E+E+E+E+E+E(/* )AI~~%! II!, $ #   +&hsA.........#//( /( /s?C 4C D:"AD55D:crtjz} stjdnt d dzD]} rH|tjz }|dkr |i|cS st  r dn |i|cSX#$rL}  ||}|s }| kr s r d|  ||Yd}~d}~wwxYwdS)Nrrrr)r?r@rrrrJrrs r6rPz1retry_on..decorator..wrapper_syncs 6>++g5!- """1i!m,,$ )$ ) ) 5)1DN4D4D)D)A--#'4#8#8#8888#)"&2 2!$" # $F!"!"!" $tT4V44444 )))#/+7<Q+?+?(/* )AI~~%! II!, $ #   + a(((#)'$ )$ )s%B.)BC, AC''C,rSrrprQ) rDrNrPrrDrdrerrrs ` r6rUzretry_on..decorators   + /+ /+ /+ /+ /+ /+ /+ /+ /+ /+ /  + /Z   ' )' )' )' )' )' )' )' )' )' )' )  ' )R  &t , ,   r5)rrk)rrerdrrrDrrUs``````` r6rurusz$  7# $ $:8999\ \ \ \ \ \ \ \ \ \ \ | r5ctjfd}tjfd}tjr|n|S)zhIf func throws an exception it is catched, converted to a string and returned as a result of a call.crK |i|d{VS#t$r}t|cYd}~Sd}~wwxYwrrreprrNrOrrDs r6rNz,stub_unexpected_error..wrapper_async5sg t.v........ .   77NNNNNN s  6166cb |i|S#t$r}t|cYd}~Sd}~wwxYwrrrs r6rPz+stub_unexpected_error..wrapper_sync<sQ 4((( (   77NNNNNN s .)..r)rDrNrPs` r6stub_unexpected_errorr1s_T _T $7== O==<Or5c2 tjfd}|S)zkA decorator that logs uncaught exceptions ignoring them otherwise. CancelledError is not handled. Nctjfd}tjfd}tjr|S|S)Nc K |i|d{VS#tj$r$r'}dtdd|Yd}~dSd}~wwxYwNzIgnoring exception from %s: %sr/r)rprrrNrOrrr log_handlers r6rNz>log_error_and_ignore..decorator..wrapper_asyncOs !T426222222222)       4D.&99 s AA  Ac t |i|S#$r'}dtdd|Yd}~dSd}~wwxYwr)rrs r6rPz=log_error_and_ignore..decorator..wrapper_sync\s tT,V,,,    4D.&99 s 727r)rrNrPrrs` r6rUz'log_error_and_ignore..decoratorNs                          &t , ,   r5)r_r)rrrUs`` r6log_error_and_ignorerFs9 l       < r5cfd}|S)z'Abort the agent service on *exception*.cLtjfd}|S)NcK |i|d{VS#$r/}t|Yd}~dSd}~wwxYwr)r_r)rNrOrabortrrs r6rQz2abort_agent_on..decorator..wrapperss !T426222222222     ###  s A$AArR)rrQrrs` r6rUz!abort_agent_on..decoratorrsC            r5r4)rrrUs`` r6abort_agent_onros*       r5cRtjdd|S)zPascalCase to snake_casez([a-z])([A-Z])z\1_\2)r$subrK)strings r6 snake_casers# 6"Hf 5 5 ; ; = ==r5g?g?cHdt|vS)Nr)rorK)rs r6_is_db_locked_errorrs s3xx~~'' ''r5)exec_expr_with_empty_iterc'K|s|rdg}nt|t}ddlm}|j5|D]}||g|REd{V ddddS#1swxYwYdS)a] Get iterator over results of sql expression expr. Given iterable will be split for chunks and we will return iterator containing results of all split queries. Useful for sql selects with in_() in order to avoid too many sql variables error. If exec_expr_with_empty_iter is True and iterable is None(empty) we will process expression once, passing here chunk=None expr(None, *args) :param expr: :param iterable: :param exec_expr_with_empty_iter: if iterable is None(empty) process given expression once, passing here chunk=None expr(None, *args) :return: Nr rinstance)rCHUNK_SIZE_SQL_QUERYdefence360agent.modelrdb transaction)exprr rrNchunksrrs r6get_results_iterable_expressionrs& L1L 6JKKK......  " "** * *EtE)D))) ) ) ) ) ) ) ) ) *******************sA##A'*A'rctt||ddlmd}t t |t dzdfd}|S) a Get number of results of sql expression expr. Given iterable will be split for chunks and we will return number of results of all split queries. Useful for sql delete with in_() in order to avoid too many sql variables error. The iterable is materialized BEFORE the database transaction opens, and the transaction is retried on transient SQLite lock errors. This matters because callers commonly pass a generator that does its own SELECTs (e.g. ``MalwareHit.delete_hits(get_outdated_entries(...))``): in SQLite WAL mode, the read snapshot taken inside a transaction becomes stale as soon as another writer commits, and the subsequent write raises SQLITE_BUSY_SNAPSHOT, which PRAGMA busy_timeout does *not* cover. rrrcttd|dz zzt}td||t |t j|dS)Nrbrz;SQLite lock contention, retrying in %.3fs (retry %d/%d): %s)minDB_LOCK_RETRY_BACKOFF_BASEDB_LOCK_RETRY_BACKOFF_MAXr_rDB_LOCK_MAX_RETRIESr?r)rattemptbackoffs r6_backoffz-execute_iterable_expression.._backoffsd &!! *< = %    I         7r5rc t|Sr)r)rrs r6rXz-execute_iterable_expression..s*=c*B*Br5)rerdrcd}j5D] }||gRz }! dddn #1swxYwY|Sr)rrexecute)rrrNrrrs r6 _execute_allz1execute_iterable_expression.._execute_alls [ $ $ & & 8 8 8 844----55777 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8s$AAA)rrrrrrurr)rr r rNrrrrs` ` @@r6execute_iterable_expressionrs$/(zBBB C CF......   %)BB     <>>r5cXtj|dddzS)Nr\nr)rfsencodergrs r6encode_filenamers% ;t||D%00 1 1E 99r5cbtj|ddddS)Nr(rr)rfsdecodergrs r6decode_filenamer s+ ;t  SbS ! ) )% 6 66r5cNtjtj|Sr)base64 b64encoderrrs r6base64_encode_filenamer s  BK-- . ..r5b64namechttjtj|Sr)rrrr  b64decode)rs r6base64_decode_filenamers%  F,W5566 7 77r5cV tj|}n#t$rd}YnwxYw|S)zS Like pwd.getpwnam(username) but returns None instead of raising KeyError. N)rrr)rrs r6rrsAh''  Ms  &&c>tt|||S)zH Put the specified `value` inside the [`low`, `high`] interval. )maxr)rlowhighs r6cliprs s5$ % %%r5Background task failedc| tj} |dS#tj$rYdSt $r}|d||Yd}~dSd}~wwxYw)a[ Callback for asyncio.Future that logs exceptions and ignores CancelledError. Use this as a done_callback for asyncio tasks/futures: future.add_done_callback(log_future_errors) Or with custom logging: future.add_done_callback( lambda f: log_future_errors(f, logger.warning, "Upload failed") ) Nz%s: %s)r_rrrprr)futrrUrs r6rVrV sn *  !     *** Hgq)))))))))*s&A A AAr.crfd}||i|}|||S)z Use this function in plugin initialization instead of loop.create_task to be able to see the exceptions from the specified coroutine. c|sA|/d||ddSdSdS)Nz1Unhandled exception during plugin initialization!)rUrr])rZrr)r]rs r6_log_exceptionz6create_task_and_log_exceptions.._log_exception+sv~~ DNN$4$4$@  ' 'L!%!1!1        $@$@r5) create_taskr\)rrrNrOrnew_tasks` r6create_task_and_log_exceptionsr "sY     d 5f 5 566H ~... Or5cfd}|S)a5 Create coroutine from regular function Useful to pass functions to APIs requiring coroutines Note: coroutine will still block event loop in main thread. For most blocking functions, run_in_executor should be considered instead :param function: :return: coroutine running function cK|i|Srr4)rNrOfunctions r6rzmake_coro..coroFsx((((r5r4)r#rs` r6 make_coror$<s#))))) Kr5cK|tkr tj}n tj}|dt |ddx}rd|dnd||t jtd{VdS)Nz7Failed to copy data%s to modsec ruleset dir %r, try: %srz ()r)COPY_TO_MODSEC_MAXTRIESr_rrrrpr_MODSEC_COPY_FAILURE_TIMEOUT)rrrDfns r6log_failed_to_copy_to_modsecr*Ps ###lnCA$S*d;;;rD R "   -4 5 5555555555r5) err_buf_sizec 4Kd}t|}tj|dtjjtjjd|d{V} tj||j||j23d{V}|WV 6 |d{V}|dkr%t||dd |dS#|d{V}|dkr%t||dd |wxYw)z Start *cmd*, yield its stdout line by line [b' '] If *cmd* return nonzero exit status, raise CheckRunError with the last *err_buf_size* lines from stderr. cJK|23d{V}||6dSr)append)pipebufr?s r6read_pipe_intoz1readlines_from_cmd_output..read_pipe_intohsL       $ JJt    $$s")maxlenT)rjrhriNrr5) rrprtrhrmrrirhryrr)rr+ror1err_bufr~r?rs r6readlines_from_cmd_outputr4^s<(((G/ !&!&         D I NN4;@@AAA+       $JJJJJ&+ 99;;&&&&&& ?? Cchhw6G6GHH H ? 99;;&&&&&& ?? Cchhw6G6GHH H H H H Hs*C:BCADrb)rddelaycKtd|dzD]3}||d{V}|s!||krtj|d{V0|cSdS)z Retry *predicate_coro(*args)* until it becomes true, but no more than *max_tries* attempts. Sleep for *delay* seconds before the next *predicate_coro()* call. Return whether the predicate became true. rN)rrpr)predicate_corordr5rNrrs r6finally_happenedr8sIM**%~t,,,,,,, 'I---&& & & & & & & &  r5'cKt|dD]-\}}|WV||zdkrtjdd{V.dS)z6Yield to the event loop every *chunk_size* iterations.r)rErN) enumeraterpr)r r ritems r6 nice_iteratorr=soXQ///##4 Nq -"" " " " " " " "##r5ceZdZdZdZdZdS)LazyLocka Descriptor object to share async Lock between client objects. Used in order to achieve lazy evaluation of the lock and share state between it's clients. Using asyncio.Lock in client code directly: >>> class Foo: >>> lock = asyncio.Lock() leads to an unclear error ([Errno 9] Bad file descriptor), when trying to move this Lock during demonization process. cd|_dSr)rr>s r6rzLazyLock.__init__s  r5cN|jstj|_|jSr)rrpr)rrowners r6__get__zLazyLock.__get__s!z ( DJzr5N)r-r.r/r]rrCr4r5r6r?r?s<  r5r?c|}|rd|vsd|vrdS|dd\}}||fS)zOParse RPM output line, return (package_name, version) or None if not installed.z not installed: Nr)rrKr )r?pkg_nameversions r6_parse_rpm_linerHs[ ::<.wrappers\ 7>>= > > +4 T4*6*********r5r)rDrQs` r6check_disabled_firewallrSs3 4[[++++[+ Nr5> imunify-ui imunify-coreimunify-antivirusimunify360-firewallpackagescKt\}}t|}t||zddd{V}t|||S)a Retrieves the version of the specified system packages using a command and regex specific to the current system. Parameters: packages (Iterable[str]): A set of package names to retrieve version for. Returns: A dictionary mapping package names to their corresponding version strings, or None if the package is not installed or version information cannot be retrieved. rF)rrN)rNrrsafe_run_with_timeout_parse_package_info_output)rXr parse_line packages_listrs r6system_packages_infor^st-..OCNNM( mR%F &fmZ H HHr5rr\cnfd|Dfd|DS)NcXi|]&}|xdxdx#'S)rrr4)rr?r\pkgrvers r6 z._parse_package_info_output..sf  j&& &F1I S  1I S  Sr5c<i|]}||Sr4)r;)rraparseds r6rcz._parse_package_info_output.. s% 5 5 5SCC 5 5 5r5) splitlines)rrXr\rerarrbs `@@@@r6r[r[sf %%''F 6 5 5 5H 5 5 55r5cK tjt|fi||d{VS#tj$r|d|YdSwxYw)Nrz#Command %s failed: Timeout occurredr)rprrrJ)rzrrDrOs r6rZrZ#s% W ' ' ' '              17;;;rrs&+A  A nc#K|dkrtdt|}tt||x}r%|Vtt||x}#dSdS)Nrzn must be at least one)rkrrrr)r rhitbatchs r6batchedrl/s  1uu1222 hBr1 && &% r1 && &%r5rOc#RKt|D]}fd|DVdS)Nc"i|] }|| Sr4r4)rr@rOs r6rcz batched_dict..=s&&&1q!A$&&&r5)rl)rOrhrks` r6 batched_dictro;sKA''&&&&&&&&&&&''r5cn tjddgd}|d}|s?t drtjddgd}d|vrd}|S#t $r&}td |Yd}~d Sd}~wwxYw) Nhostnamez-fT)r)z.cloudwaysapps.comz.cloudwaysstagingapps.comz/usr/local/sbin/apminfo Cloudwaysz$Error while checking environment: %sF) rl check_outputrrrr:rr_r)rq _is_cloudwaysrrs r6 is_cloudwaysrv@s+  T   %'' !)) ?   %&;!rs  ********////////::::::::::222222222222''''''                     ######00000000%%%%%% GCx     8 $ $ d0116d344 "d#BCC$899 SJNN:B?? +++++D+++Q     K K K K4T    4        00 3u 0000f 0      K2    (5  5    .; ' ' ' ' ' ,   ++++++++@        :?;;;;F(,  ddd 3J $ d $Jd ddddNQ ? ?C ? ? ? ?h'h'h'h'h'h'h'h'X%[4 A A A58 A A A A A #### 38_ ####2.<     $)#         5 T         5 T     % % %--------`0E6D&3#$ % % % , ,h ,C ,) , , , ,    ########"R   !!! !"2eeeeeeeeP -.! ! ! ! ! H)))ZQ4 $>>> 3 3S 3s 3 3 3 3     $      ,0JJJJJ 26PPPPP"Q:::: :( 3 33 3S 3 3 3 3 $      7<B   $   sssslPPP*$-$&&&&R%7&>>>  !((((( 6;*****@';11111h:::777//%////8E8d8888&&&****.i(4      6 6 6%(III cIIIID=>Q      ####0#%S/D"835c?T#9"Q $s)XseU38_t%;;< <= .FFFFF FFF   " IsmI #sTz/IIII, 6  63i 6#c3h$ 667 6 #sTz/ 6 6 6 6 !,           'DcN's'''' Q ( T c     t    &&&&&r5defence360agent/utils/__pycache__/_shutil.cpython-311.opt-1.pyc0000644000000000000000000000344300000000000021232 0ustar r_jddZddlZddlZddlZddlZejeZdefdZ d dddZ dS) zHigh-level file operations.Nreturnct|to9t|o*d|vo&|tj|ko|dvS)N).z..) isinstancestrboolospathbasename)names R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/_shutil.pyis_safe_subdir_namer s]4 $ JJ $ $  $ BG$$T** * $  # F) max_triesctd|dzD]s} tj|||cS#t$rL}||ks|jtjtjfvrtd||Yd}~ld}~wwxYwdS)zMore robust shutil.rmtree. Retry on "Directory not empty" race condition: https://github.com/ansible/ansible/issues/34335#issuecomment-362995700 z Can't remove %s tree, reason: %sN) rangeshutilrmtreeOSErrorerrnoEEXIST ENOTEMPTYloggerwarning)r ignore_errorsonerrorries rrrs 1i!m $ $ H H H=}g>> > > > H H HI~~ 1"" NN=tQ G G G G G G G G H H Hs/ BABB)FN) __doc__rloggingr r getLogger__name__rr rrrrr's!!   8 $ $HHHHHHHHrdefence360agent/utils/__pycache__/_shutil.cpython-311.pyc0000644000000000000000000000344300000000000020273 0ustar r_jddZddlZddlZddlZddlZejeZdefdZ d dddZ dS) zHigh-level file operations.Nreturnct|to9t|o*d|vo&|tj|ko|dvS)N).z..) isinstancestrboolospathbasename)names R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/_shutil.pyis_safe_subdir_namer s]4 $ JJ $ $  $ BG$$T** * $  # F) max_triesctd|dzD]s} tj|||cS#t$rL}||ks|jtjtjfvrtd||Yd}~ld}~wwxYwdS)zMore robust shutil.rmtree. Retry on "Directory not empty" race condition: https://github.com/ansible/ansible/issues/34335#issuecomment-362995700 z Can't remove %s tree, reason: %sN) rangeshutilrmtreeOSErrorerrnoEEXIST ENOTEMPTYloggerwarning)r ignore_errorsonerrorries rrrs 1i!m $ $ H H H=}g>> > > > H H HI~~ 1"" NN=tQ G G G G G G G G H H Hs/ BABB)FN) __doc__rloggingr r getLogger__name__rr rrrrr's!!   8 $ $HHHHHHHHrdefence360agent/utils/__pycache__/antivirus_mode.cpython-311.opt-1.pyc0000644000000000000000000000234100000000000022607 0ustar r_j4ddlZddlZddlmZdZee cZZdS)NANTIVIRUS_MODEctjfd}tjfd}tjr|n|S)Nc6Ktrdn |i|d{VSNrargskwargsfs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/antivirus_mode.py async_wrapperzskip..async_wrappers7%CttD1CF1C1C+C+C+C+C+C+CCc&trdn|i|Srrrs r wrapperzskip..wrapper s!%=tt11d+=f+=+==r) functoolswrapsinspectiscoroutinefunction)r r rs` r skiprs_QDDDDD_Q>>>>>$7:: G==Gr)rr defence360agent.contracts.configrrenableddisabledrr rsS;;;;;; H H H#$6rdefence360agent/utils/__pycache__/antivirus_mode.cpython-311.pyc0000644000000000000000000000234100000000000021650 0ustar r_j4ddlZddlZddlmZdZee cZZdS)NANTIVIRUS_MODEctjfd}tjfd}tjr|n|S)Nc6Ktrdn |i|d{VSNrargskwargsfs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/antivirus_mode.py async_wrapperzskip..async_wrappers7%CttD1CF1C1C+C+C+C+C+C+CCc&trdn|i|Srrrs r wrapperzskip..wrapper s!%=tt11d+=f+=+==r) functoolswrapsinspectiscoroutinefunction)r r rs` r skiprs_QDDDDD_Q>>>>>$7:: G==Gr)rr defence360agent.contracts.configrrenableddisabledrr rsS;;;;;; H H H#$6rdefence360agent/utils/__pycache__/async_utils.cpython-311.opt-1.pyc0000644000000000000000000000352600000000000022122 0ustar r_jLddlmZmZmZddlZGddZdedefdZdS))ListUnionTupleNc<eZdZdeeeffdZdZdZdZ dS) AsyncIteratedatac.t||_dSN)iterqueueselfrs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/async_utils.py__init__zAsyncIterate.__init__s$ZZ c|Sr )rs r __aiter__zAsyncIterate.__aiter__ s rcPK|d{V}||Str ) fetch_dataStopAsyncIterationr s r __anext__zAsyncIterate.__anext__ s8__&&&&&&&&  K$ $rcZK t|j}n#t$rd}YnwxYw|Sr )nextr StopIteration)ritems rrzAsyncIterate.fetch_datasE  ##DD   DDD  s  ((N) __name__ __module__ __qualname__rrrrrrrrrrrrse U4;/    %%%rrtasksreturncLKtj|d{V}t|Sr )asynciogatherr)r resultss rr$r$s2NE*******G   r)typingrrrr#rr$rrrr's%%%%%%%%%%,!!,!!!!!!rdefence360agent/utils/__pycache__/async_utils.cpython-311.pyc0000644000000000000000000000352600000000000021163 0ustar r_jLddlmZmZmZddlZGddZdedefdZdS))ListUnionTupleNc<eZdZdeeeffdZdZdZdZ dS) AsyncIteratedatac.t||_dSN)iterqueueselfrs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/async_utils.py__init__zAsyncIterate.__init__s$ZZ c|Sr )rs r __aiter__zAsyncIterate.__aiter__ s rcPK|d{V}||Str ) fetch_dataStopAsyncIterationr s r __anext__zAsyncIterate.__anext__ s8__&&&&&&&&  K$ $rcZK t|j}n#t$rd}YnwxYw|Sr )nextr StopIteration)ritems rrzAsyncIterate.fetch_datasE  ##DD   DDD  s  ((N) __name__ __module__ __qualname__rrrrrrrrrrrrse U4;/    %%%rrtasksreturncLKtj|d{V}t|Sr )asynciogatherr)r resultss rr$r$s2NE*******G   r)typingrrrr#rr$rrrr's%%%%%%%%%%,!!,!!!!!!rdefence360agent/utils/__pycache__/benchmark.cpython-311.opt-1.pyc0000644000000000000000000000271500000000000021516 0ustar r_j4ddlZddlmZGddZdS)N) TracebackTypecheZdZd dZdeedzdedzdedzddfdZede fdZ dS) BenchmarkreturnNc6tj|_|SN)time monotonic_ns start_timeselfs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/benchmark.py __enter__zBenchmark.__enter__s+-- exc_typeexc_valexc_tbc^tj|_|j|jz |_dSr)r r end_timer elapsed_time_ns)r rrrs r__exit__zBenchmark.__exit__ s+ )++ #}t>rc|jdzS)Ngư>)rr s relapsed_time_mszBenchmark.elapsed_time_mss#d**r)rN) __name__ __module__ __qualname__rtype BaseExceptionrrpropertyfloatrrrrrs?}%,?%?$ ?  ????++++X+++rr)r typesrrr!rrr#sR ++++++++++rdefence360agent/utils/__pycache__/benchmark.cpython-311.pyc0000644000000000000000000000271500000000000020557 0ustar r_j4ddlZddlmZGddZdS)N) TracebackTypecheZdZd dZdeedzdedzdedzddfdZede fdZ dS) BenchmarkreturnNc6tj|_|SN)time monotonic_ns start_timeselfs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/benchmark.py __enter__zBenchmark.__enter__s+-- exc_typeexc_valexc_tbc^tj|_|j|jz |_dSr)r r end_timer elapsed_time_ns)r rrrs r__exit__zBenchmark.__exit__ s+ )++ #}t>rc|jdzS)Ngư>)rr s relapsed_time_mszBenchmark.elapsed_time_mss#d**r)rN) __name__ __module__ __qualname__rtype BaseExceptionrrpropertyfloatrrrrrs?}%,?%?$ ?  ????++++X+++rr)r typesrrr!rrr#sR ++++++++++rdefence360agent/utils/__pycache__/buffer.cpython-311.opt-1.pyc0000644000000000000000000001020200000000000021023 0ustar r_jtGddeZGddeZGddeZGddZdS) ceZdZdS)LineBufferOverflowN__name__ __module__ __qualname__Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/buffer.pyrrDr rc4eZdZdZdZdZdZdZdZdZ dS) LineBufferz Allows to accumulate data, and than iterate over it getting tokens split by line breaks ' '. If at the end there is no line break, the data will sit in the line buffer until more data with line break comes in. cd|_dSNbufselfs r __init__zLineBuffer.__init__ r ct|jt|z|jkr.d|_td|j|xj|z c_dS)Nrz,LineBuffer exceeded maximum size of {} bytes)lenrMAX_SIZErformatrdatas r appendzLineBuffer.appendsi tx==3t99 $t} 4 4DH$>EEM  Dr c|SNrrs r __iter__zLineBuffer.__iter__ r c|jd}|dkr(|jd|}|j|dzd|_|St)N )rfind StopIteration)rposresults r __next__zLineBuffer.__next__sNhmmD!! "99Xae_Fxa *DHMr cd|_dSrrrs r cleanzLineBuffer.clean'rr N) rrr__doc__rrrr!r,r.rr r r r sp Hr r ceZdZdS)SizeBufferOverflowNrrr r r1r1+r r r1c,eZdZdZddZdZdZdZdS) SizeBufferrc"d|_||_dS)Nr )_buf _size_len)rsize_lens r rzSizeBuffer.__init__2s !r ct|jt|z|jkr.d|_td|j|xj|z c_dS)Nr z,SizeBuffer exceeded maximum size of {} bytes)rr6rr1rrs r rzSizeBuffer.append6si ty>>CII % 5 5DI$>EEM  T r c|Sr rrs r r!zSizeBuffer.__iter__@r"r cD|jstt|jd|jd}t |j|jd|kr:|j|j|j|z}|j|j|zd|_|St)Nbig)r6r)int from_bytesr7r)rsizers r r,zSizeBuffer.__next__Csy  ~~di(8$.(895AA ty))* + +t 3 39T^dnt.CCDD $.4"7"9"9:DIKr N)r4)rrrrrrr!r,rr r r3r3/sZH""""r r3N) Exceptionrobjectr r1r3rr r rBs        ########L        r defence360agent/utils/__pycache__/buffer.cpython-311.pyc0000644000000000000000000001020200000000000020064 0ustar r_jtGddeZGddeZGddeZGddZdS) ceZdZdS)LineBufferOverflowN__name__ __module__ __qualname__Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/buffer.pyrrDr rc4eZdZdZdZdZdZdZdZdZ dS) LineBufferz Allows to accumulate data, and than iterate over it getting tokens split by line breaks ' '. If at the end there is no line break, the data will sit in the line buffer until more data with line break comes in. cd|_dSNbufselfs r __init__zLineBuffer.__init__ r ct|jt|z|jkr.d|_td|j|xj|z c_dS)Nrz,LineBuffer exceeded maximum size of {} bytes)lenrMAX_SIZErformatrdatas r appendzLineBuffer.appendsi tx==3t99 $t} 4 4DH$>EEM  Dr c|SNrrs r __iter__zLineBuffer.__iter__ r c|jd}|dkr(|jd|}|j|dzd|_|St)N )rfind StopIteration)rposresults r __next__zLineBuffer.__next__sNhmmD!! "99Xae_Fxa *DHMr cd|_dSrrrs r cleanzLineBuffer.clean'rr N) rrr__doc__rrrr!r,r.rr r r r sp Hr r ceZdZdS)SizeBufferOverflowNrrr r r1r1+r r r1c,eZdZdZddZdZdZdZdS) SizeBufferrc"d|_||_dS)Nr )_buf _size_len)rsize_lens r rzSizeBuffer.__init__2s !r ct|jt|z|jkr.d|_td|j|xj|z c_dS)Nr z,SizeBuffer exceeded maximum size of {} bytes)rr6rr1rrs r rzSizeBuffer.append6si ty>>CII % 5 5DI$>EEM  T r c|Sr rrs r r!zSizeBuffer.__iter__@r"r cD|jstt|jd|jd}t |j|jd|kr:|j|j|j|z}|j|j|zd|_|St)Nbig)r6r)int from_bytesr7r)rsizers r r,zSizeBuffer.__next__Csy  ~~di(8$.(895AA ty))* + +t 3 39T^dnt.CCDD $.4"7"9"9:DIKr N)r4)rrrrrrr!r,rr r r3r3/sZH""""r r3N) Exceptionrobjectr r1r3rr r rBs        ########L        r defence360agent/utils/__pycache__/check_db.cpython-311.opt-1.pyc0000644000000000000000000003246600000000000021314 0ustar r_j"ddlZddlZddlZddlmZddlmZddlmZddlm Z m Z ddl m Z ddl mZddlmZdd lmZdd lmZejeZGd d eZd ZdZddZdZdZdZdZ dZ!ddZ"de de#e$ddfdZ%dS)N)suppress)datetime)copy)connect DatabaseError)SqliteExtDatabase)app) simple_rpc)Model)simplificationceZdZdS)OperationErrorN)__name__ __module__ __qualname__S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_db.pyrrsDrrz.Blank database will be created on agent start cVtj}tjrt dt j|st d|dtt|rt|}|st dt|}t d|zt j||st d|dtt||}|st dt|r+t j|t d tzt d |zt j| t d t!jt%s+t j|t d tzdS#t&$r3}t j|t d |dtd}~wwxYwdS)NzCannot perform database check and backup while agent is running. Please, stop the imunify360 agent with `service imunify360 stop`zDB z is not exists. z{Cannot proceed without backup copy of the database.Please contact imunify360 support team at https://cloudlinux.zendesk.comz*Removing original corrupted database at %sz0Cannot dump database to sql. Old DB backuped at z. zRLoading dump to new database failed. Database will be recreated during migrations.zBRestored database is still corrupt. Removing restored database. %sz0Database restored successfully. Removing dump %sz*Performing migrations on restored databasez;Restored database does not contain all necessary tables. %sz(Migrations on restored database failed: )r PATHr is_runningrospathisfileWORKAROUND_MSGis_db_corrupted make_backup dump_to_sqlloggerinforemove load_from_sqlr migrateall_tables_are_present Exception)basebackupdumprestoredes rcheck_and_repairr+su :D> O   W^^D ! !9n)-~~ >    4 4  &&F $5 t$$D KKDtK L L L IdOOO( $nvv~~/ )t44(: #8,,Ih'''(02@A  FM $KK LMMM"*,,,233 $,!#12!IdOOO(.11nn.O4 4 s-G)) H&3.H!!H&ctj}d||d}|r|d|zzS|S)a >>> mark_with_timestamp('/var/imunify360/imunify360.db') '/var/imunify360/imunify360.db_2017-09-26_03:33:44.705967' >>> mark_with_timestamp('/var/imunify360/imunify360.db', extension='sql') '/var/imunify360/imunify360.db_2017-09-26_03:34:01.098544.sql' z{}_{}_z.%s)rnowformat isoformat)filename extensioninstantbasenames rmark_with_timestampr5`sMlnnG~~h(9(9#(>(>??H%)+++rctd|zd}t|5} |d}t |}d|vrtdd}n2#t $r%}td|Yd}~nd}~wwxYw|cdddS#1swxYwYdS)NzDatabase %s integrity check...TzPRAGMA INTEGRITY_CHECK;okz#Database integrity check succeeded.FzDatabaseError detected: %s)rr rexecutenextrwarning)db_path is_corrupted connectioncursorresultr*s rrros KK07:;;;L    Z <''(ABBF&\\Fv~~ ABBB$  < < < NN7 ; ; ; ; ; ; ; ; <                  s;B6AA65B66 B%B B6 B%%B66B:=B:cXt|d}td|z t|d5}t |5}|D]}|| dddn #1swxYwYdddn #1swxYwYn}#ttf$ri}t d|ztt5tj |dddn #1swxYwYd}Yd}~nd}~wwxYw|S)Nsql)r2z!Dumping imunify360 database to %swz(Error during dump: %s. Operation aborted) r5rr openriterdumpwriterOSErrorerrorrrr!)r;dumpfiler(r=rowr*s rrr~s"7e<<??? (C  D''*:*: j!**,,   3                                7 # ?!CDDD g    Ih                    OsB-B!-B > B! B B!B B! B-!B%%B-(B%)B--D'>1D"/D D"D D"D D""D'c tj|rtddStd|d|dt |d5}t|5} |}| |ns#t$rf}t |tt5tj|dddn #1swxYwYd}Yd}~nd}~wwxYwdddn #1swxYwYdddn #1swxYwY|S)Nz^Database already exists. Loading dump to existing database may cause errors. Operation abortedz Reading dump z into new database z...r)rrexistsrr:r rCrread executescript MemoryErrorrGrrFr!)r;rHr(r=rAr*s rr"r"s w~~g ;   t KKK5=XXwwwG h   gg&6&6 * ))++C  $ $S ) ) ) )    LLOOO'"" # # '""" # # # # # # # # # # # # # # #GGGGGG                                 Ns-D?=D(?)B)(D() D3.D!D 6 DD D D DD(DD( D?(D, ,D?/D, 0D??EEctd|zt|d} t||td|znt#t$rg}td|t t5tj |dddn #1swxYwYd}Yd}~nd}~wwxYw|S)NzMaking backup of the %s...r'z$Database copied successfully to: %s zMaking backup failed: %s) rr r5rr%rGrrFrr!)r;backup_filenamer*s rrrs KK,w6777)'8<B; ?C  CctdtjdtjD}t d|DrtddStddS)NzDVerifying that db schema is up-to-date and all tables are present...c6g|]}tj|Srr get_models.0modules r z*all_tables_are_present..s3     %f - -   rc3>K|]}|VdSN) table_exists)rWmodels r z)all_tables_are_present..s. 4 4E5     4 4 4 4 4 4rzAll tables are presentTzSome tables are missing in db.F)rr itertoolschainr MODULES_WITH_MODELSallrG)modelss rr$r$s KKN_  1   F  4 4V 4 4 444 ,---t 5666urreturnctjjtjt dg}tj D]\\}}t d|tjj d||f| |]ttjj|t ddS)Nz#Recreating schema for linked DBs...z Attach db: %sz ATTACH ? AS ?zSchema recreated successfully.) r instancedbinitr rrr r MIGRATIONS_ATTACHED_DBS execute_sqlappendrecreate_schema_models)attached_schemasr;schemas rrecreate_schemaros##EJ/// KK56666(( OW---".. gv.    ''''>257GHHH KK011111rrgtarget_schemasc"fdtjdtjDD}td|||||tddS)Nc0g|]}|jjv|Sr)_metarn)rWr]rps rrYz*recreate_schema_models..s7     ;  / /  0 / /rc6g|]}tj|SrrTrVs rrYz*recreate_schema_models..s3)&11rz%rz%Schema models recreated successfully.)r_r`r rarr bind create_tables)rgrpmodels_to_creates ` rrlrls    _!5     KK&'''GG %&&& KK788888rr[)rdN)&loggingr_r contextlibrrshutilrsqlite3rrplayhouse.sqlite_extrdefence360agent.applicationr defence360agentr defence360agent.contracts.configr defence360agent.modelr getLoggerrrr%rrr+r5rrr"rr$roliststrrlrrrrs ********222222++++++&&&&&&222222000000  8 $ $     Y   B@@@F          4   $ 2 2 2 2 99+/99 999999rdefence360agent/utils/__pycache__/check_db.cpython-311.pyc0000644000000000000000000003246600000000000020355 0ustar r_j"ddlZddlZddlZddlmZddlmZddlmZddlm Z m Z ddl m Z ddl mZddlmZdd lmZdd lmZejeZGd d eZd ZdZddZdZdZdZdZ dZ!ddZ"de de#e$ddfdZ%dS)N)suppress)datetime)copy)connect DatabaseError)SqliteExtDatabase)app) simple_rpc)Model)simplificationceZdZdS)OperationErrorN)__name__ __module__ __qualname__S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_db.pyrrsDrrz.Blank database will be created on agent start cVtj}tjrt dt j|st d|dtt|rt|}|st dt|}t d|zt j||st d|dtt||}|st dt|r+t j|t d tzt d |zt j| t d t!jt%s+t j|t d tzdS#t&$r3}t j|t d |dtd}~wwxYwdS)NzCannot perform database check and backup while agent is running. Please, stop the imunify360 agent with `service imunify360 stop`zDB z is not exists. z{Cannot proceed without backup copy of the database.Please contact imunify360 support team at https://cloudlinux.zendesk.comz*Removing original corrupted database at %sz0Cannot dump database to sql. Old DB backuped at z. zRLoading dump to new database failed. Database will be recreated during migrations.zBRestored database is still corrupt. Removing restored database. %sz0Database restored successfully. Removing dump %sz*Performing migrations on restored databasez;Restored database does not contain all necessary tables. %sz(Migrations on restored database failed: )r PATHr is_runningrospathisfileWORKAROUND_MSGis_db_corrupted make_backup dump_to_sqlloggerinforemove load_from_sqlr migrateall_tables_are_present Exception)basebackupdumprestoredes rcheck_and_repairr+su :D> O   W^^D ! !9n)-~~ >    4 4  &&F $5 t$$D KKDtK L L L IdOOO( $nvv~~/ )t44(: #8,,Ih'''(02@A  FM $KK LMMM"*,,,233 $,!#12!IdOOO(.11nn.O4 4 s-G)) H&3.H!!H&ctj}d||d}|r|d|zzS|S)a >>> mark_with_timestamp('/var/imunify360/imunify360.db') '/var/imunify360/imunify360.db_2017-09-26_03:33:44.705967' >>> mark_with_timestamp('/var/imunify360/imunify360.db', extension='sql') '/var/imunify360/imunify360.db_2017-09-26_03:34:01.098544.sql' z{}_{}_z.%s)rnowformat isoformat)filename extensioninstantbasenames rmark_with_timestampr5`sMlnnG~~h(9(9#(>(>??H%)+++rctd|zd}t|5} |d}t |}d|vrtdd}n2#t $r%}td|Yd}~nd}~wwxYw|cdddS#1swxYwYdS)NzDatabase %s integrity check...TzPRAGMA INTEGRITY_CHECK;okz#Database integrity check succeeded.FzDatabaseError detected: %s)rr rexecutenextrwarning)db_path is_corrupted connectioncursorresultr*s rrros KK07:;;;L    Z <''(ABBF&\\Fv~~ ABBB$  < < < NN7 ; ; ; ; ; ; ; ; <                  s;B6AA65B66 B%B B6 B%%B66B:=B:cXt|d}td|z t|d5}t |5}|D]}|| dddn #1swxYwYdddn #1swxYwYn}#ttf$ri}t d|ztt5tj |dddn #1swxYwYd}Yd}~nd}~wwxYw|S)Nsql)r2z!Dumping imunify360 database to %swz(Error during dump: %s. Operation aborted) r5rr openriterdumpwriterOSErrorerrorrrr!)r;dumpfiler(r=rowr*s rrr~s"7e<<??? (C  D''*:*: j!**,,   3                                7 # ?!CDDD g    Ih                    OsB-B!-B > B! B B!B B! B-!B%%B-(B%)B--D'>1D"/D D"D D"D D""D'c tj|rtddStd|d|dt |d5}t|5} |}| |ns#t$rf}t |tt5tj|dddn #1swxYwYd}Yd}~nd}~wwxYwdddn #1swxYwYdddn #1swxYwY|S)Nz^Database already exists. Loading dump to existing database may cause errors. Operation abortedz Reading dump z into new database z...r)rrexistsrr:r rCrread executescript MemoryErrorrGrrFr!)r;rHr(r=rAr*s rr"r"s w~~g ;   t KKK5=XXwwwG h   gg&6&6 * ))++C  $ $S ) ) ) )    LLOOO'"" # # '""" # # # # # # # # # # # # # # #GGGGGG                                 Ns-D?=D(?)B)(D() D3.D!D 6 DD D D DD(DD( D?(D, ,D?/D, 0D??EEctd|zt|d} t||td|znt#t$rg}td|t t5tj |dddn #1swxYwYd}Yd}~nd}~wwxYw|S)NzMaking backup of the %s...r'z$Database copied successfully to: %s zMaking backup failed: %s) rr r5rr%rGrrFrr!)r;backup_filenamer*s rrrs KK,w6777)'8<B; ?C  CctdtjdtjD}t d|DrtddStddS)NzDVerifying that db schema is up-to-date and all tables are present...c6g|]}tj|Srr get_models.0modules r z*all_tables_are_present..s3     %f - -   rc3>K|]}|VdSN) table_exists)rWmodels r z)all_tables_are_present..s. 4 4E5     4 4 4 4 4 4rzAll tables are presentTzSome tables are missing in db.F)rr itertoolschainr MODULES_WITH_MODELSallrG)modelss rr$r$s KKN_  1   F  4 4V 4 4 444 ,---t 5666urreturnctjjtjt dg}tj D]\\}}t d|tjj d||f| |]ttjj|t ddS)Nz#Recreating schema for linked DBs...z Attach db: %sz ATTACH ? AS ?zSchema recreated successfully.) r instancedbinitr rrr r MIGRATIONS_ATTACHED_DBS execute_sqlappendrecreate_schema_models)attached_schemasr;schemas rrecreate_schemaros##EJ/// KK56666(( OW---".. gv.    ''''>257GHHH KK011111rrgtarget_schemasc"fdtjdtjDD}td|||||tddS)Nc0g|]}|jjv|Sr)_metarn)rWr]rps rrYz*recreate_schema_models..s7     ;  / /  0 / /rc6g|]}tj|SrrTrVs rrYz*recreate_schema_models..s3)&11rz%rz%Schema models recreated successfully.)r_r`r rarr bind create_tables)rgrpmodels_to_creates ` rrlrls    _!5     KK&'''GG %&&& KK788888rr[)rdN)&loggingr_r contextlibrrshutilrsqlite3rrplayhouse.sqlite_extrdefence360agent.applicationr defence360agentr defence360agent.contracts.configr defence360agent.modelr getLoggerrrr%rrr+r5rrr"rr$roliststrrlrrrrs ********222222++++++&&&&&&222222000000  8 $ $     Y   B@@@F          4   $ 2 2 2 2 99+/99 999999rdefence360agent/utils/__pycache__/check_lock.cpython-311.opt-1.pyc0000644000000000000000000000345600000000000021654 0ustar r_jX.ddlZddlZddedefdZdZdS)NFcheck_lock_periodjitterc.|s|jdd|r\tjt |}|ttj|z|z|S|ttj|zdSt||x}dkr8|ttj|zdS|S)NT)parentsexist_okr) existsparentmkdirrandom randrangeint write_textstrtimeis_period_passed)r lock_filerdelay time_lefts U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_lock.py check_lockrs     td;;;  $S):%;%;<r!sW #$"%%%%%rdefence360agent/utils/__pycache__/check_lock.cpython-311.pyc0000644000000000000000000000345600000000000020715 0ustar r_jX.ddlZddlZddedefdZdZdS)NFcheck_lock_periodjitterc.|s|jdd|r\tjt |}|ttj|z|z|S|ttj|zdSt||x}dkr8|ttj|zdS|S)NT)parentsexist_okr) existsparentmkdirrandom randrangeint write_textstrtimeis_period_passed)r lock_filerdelay time_lefts U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/check_lock.py check_lockrs     td;;;  $S):%;%;<r!sW #$"%%%%%rdefence360agent/utils/__pycache__/cli.cpython-311.opt-1.pyc0000644000000000000000000003757700000000000020351 0ustar r_j9$ddlmZddlZddlZddlZddlZddlZddlZddddZdZ dZ dZ d d gZ d \Z ZZed ed iZe dee ee iZdZGddZdZdZdZdZdZdZdZdZdZdZdZdZdZ dZ!ided ed!ed"ed#ed$ed%ed&ed'ed(ed)ed*ed+ed,ed-ed.ed/eeee e!d0Z"d1hZ#d2Z$d;d4Z%d5Z&dzpager..s5>>QBGNN1,=,=>q>>>>>>)inputstdout) renvirongetnextPAGERSprint subprocessrunencodesysr)datapagers rr)r)st JNN>>&>>>EE  E } d wdkkmmCJGGGGGGrcdeZdZdZ ddZejfdZedZ edZ dS) TablePrintercbi|_tt|_i|_i|_dSr)_headersrlist_mappers_right_aligned_widths)selfs r__init__zTablePrinter.__init__'s+ #D))   rNFc|r ||j|<|r ||j|<||j|<|r|n||j|<dSr)r/r1r0upperr-)r2fieldmappers max_width right_alignheaders rset_field_propertiesz!TablePrinter.set_field_properties-s\  +#*DM%  ,"+DL %0E")/BvvU[[]] erc fd|D}d|D}g}|D]}g}t|D]\} } || } j| D] } | | } t| } t | || krQj| } | r#t | | kr| d| dz dz} t | || <|| ||t||d|D]?}t||j | d@dS)Ncjg|]/}j||0S)r-r r5)rr6r2s r z&TablePrinter.print..=s3OOOu4=$$UEKKMM::OOOrc,g|]}t|Sr>)len)rr6s rr?z&TablePrinter.print..>s222#e**222rr z...F) enumerater r/strrAr1appendr# _format_rowr0)r2fieldsitemsfileheaderswidthsrowsitemrowir6vmapperr8s` rr#zTablePrinter.print<sOOOOOOO22'222  DC%f--  5HHUOO"mE2""Fq AAFFq66F1I%% $ 0 0 7 7I 7SVVi%7%7o A o.6 #AF1I 1 KK     dw66777  C   !4!8!8!F!F      rcZ|r||S||Sr)rjustljust)valuewidthr9s r _add_paddingzTablePrinter._add_paddingVs.  &;;u%% %{{5!!!rchfdt|D}d|S)NcZg|]'\}}t||(Sr>)r+rV)rrNrT right_alignedrJs rr?z,TablePrinter._format_row..^sC   5  % %eVAY F F   rz )rBjoin)columnsrJrYcolss `` rrEzTablePrinter._format_row\sK     %g..   yyr)NNFN) __name__ __module__ __qualname__r3r;r'rr# staticmethodrVrEr>rrr+r+&s C C C C), 4""\" \rr+c||ndS)Nzn/ar>rTs rn_arces%5550rc(|t|n|Sr)intrbs rto_intrfis*3u:::5rcfd}|S)Nc\t|tr|S|Sr) isinstancedictr )rTr6s r extractorz extract_field..extractorns, eT " " $99U## # rr>)r6rks` r extract_fieldrlms$ rct}|D] }|j| |d|D|dS)Ncg|] }|d S)rr>)rrLs rr?zprint_table..zs111Ta111r)r+r;r#)r( field_propstablepropss r print_tablerrvsV NNE++""E*** KK11[111488888rcdtgfdtgfdtdgfdtgfdtgfdtgff}t||dS)N timestampabusercountrycodetimesnameseverity)rfrcrlrrr(ros rprint_incidentsr|}se vh C5 ]6**+, 3% # cU Kk"""""rcttj}|D],}|dd}|dkr ||z |d<'d|d<-dS)N expirationrttl)retimer )r(nowrLr~s radd_ttlrse dikk  CXXlA.. >>$s*DKKDKK rcnt|dddtdgff}t||dS)Niprrvrwrrlrrr{s rprint_graylistrsE DMMM ]6**+,K k"""""rcrt|dddtdgfddf}t||dS)Nrrrvrw) imported_from)commentrr{s r print_bwlistrsK DMMM ]6**+, Kk"""""rct|ttfrt|rt }t|dt rdt |d}|dtdg| ||dS|D]}t|dSdSt|dS)Nrrvrw)r7) rir.tuplerAr+rjsortedkeysr;rlr#)r(printerrrLs r guess_printerrs$u &&  t99 "nnG$q'4(( d1gllnn--,, f(=(='>- dD)))))   D$KKKK    d rct|trt|dSttj|ddS)NF)default_flow_style)rirCr#yamldumpr(s r yaml_printerrsF$9 d  di77788888rct|trt|dSttj|dSr)rirCr#jsondumpsrs r json_printerrsA$  d  djrc Vt|tr*td|ddSg}|D]B}|d|d|d|drdndCtd |dS) Nz Status: {}statuszEvent: {}, Path: {}{}eventrnativez native )rirjr#formatrDrZ)r(resulthooks r hook_printerrs$ ! l!!$x.1122222  D MM'..ML"&x.8JJb     dii     rc|stddSdddd}|D]}||dxxdz cc<tdjdi|tt|ddS) NzNo users targeted.r) succeededskippedfailedrz:{succeeded} succeeded, {skipped} skipped, {failed} failed.))user)r)reasonr>)r#rrr)rGcountsrLs rwaf_set_printerrs  "###a 8 8F$$tH~!# KDK      GGG<=====rczd|ddz}|dds|dz }t|td|ddz|d pg}|d t|}t||kr1td |t|n"td |t|std dSt |ddS)Nz Global WAF: global_wafunknownsecurity_plugin_enabledTz (plugin off)zDefault (no override): global_waf_defaultrG total_countzTotal accounts: {} (showing {})zTotal accounts: {}z No accounts.))ry) waf_status)source)wp_sites)r r#rArrr)rr:rGtotals rwaf_status_printerrs> fjjyAA AF ::/ 6 6"/! &MMM !FJJ/CY$O$OO JJw   %2E JJ}c%jj 1 1E 5zzE /66uc%jjIIJJJJ "))%00111 GGG  n @r)configshow)eularr ) whitelist)rrr.) blacklist)rrr.)graylist)rrr.)malwarez on-demandr)feature-managementdefaults)rr)renable)rdisable)rr )radd)rdelete))rr.)rz add-native)wordpress-pluginwafsetrrrrc@|d|dndS)NrGOKr)rs r_get_default_outputrs!$jj11=6'??4GrFcZ|rtni}ttj|fi|dSr)PRETTY_JSON_ARGSr#rr)r is_verbose pretty_argss r_print_json_responser#s8&08""bK $*V + +{ + +,,,,,rct|t}|tvr ||dS|t |dS)zrrrsJ######  !%+NN { #8%Iug6 Q  ! HHH<<<<<<<<~111666999 # # #### # # #"999    ! ! ! > > > 4   e  o L       L    >  '  '  #L %l &| "<  \! " # $#((7+=+   4??HHH---- ///....(((((16 '?Bz ' ' ' ' ' ' 'rdefence360agent/utils/__pycache__/cli.cpython-311.pyc0000644000000000000000000003757700000000000017412 0ustar r_j9$ddlmZddlZddlZddlZddlZddlZddlZddddZdZ dZ dZ d d gZ d \Z ZZed ed iZe dee ee iZdZGddZdZdZdZdZdZdZdZdZdZdZdZdZdZ dZ!ided ed!ed"ed#ed$ed%ed&ed'ed(ed)ed*ed+ed,ed-ed.ed/eeee e!d0Z"d1hZ#d2Z$d;d4Z%d5Z&dzpager..s5>>QBGNN1,=,=>q>>>>>>)inputstdout) renvirongetnextPAGERSprint subprocessrunencodesysr)datapagers rr)r)st JNN>>&>>>EE  E } d wdkkmmCJGGGGGGrcdeZdZdZ ddZejfdZedZ edZ dS) TablePrintercbi|_tt|_i|_i|_dSr)_headersrlist_mappers_right_aligned_widths)selfs r__init__zTablePrinter.__init__'s+ #D))   rNFc|r ||j|<|r ||j|<||j|<|r|n||j|<dSr)r/r1r0upperr-)r2fieldmappers max_width right_alignheaders rset_field_propertiesz!TablePrinter.set_field_properties-s\  +#*DM%  ,"+DL %0E")/BvvU[[]] erc fd|D}d|D}g}|D]}g}t|D]\} } || } j| D] } | | } t| } t | || krQj| } | r#t | | kr| d| dz dz} t | || <|| ||t||d|D]?}t||j | d@dS)Ncjg|]/}j||0S)r-r r5)rr6r2s r z&TablePrinter.print..=s3OOOu4=$$UEKKMM::OOOrc,g|]}t|Sr>)len)rr6s rr?z&TablePrinter.print..>s222#e**222rr z...F) enumerater r/strrAr1appendr# _format_rowr0)r2fieldsitemsfileheaderswidthsrowsitemrowir6vmapperr8s` rr#zTablePrinter.print<sOOOOOOO22'222  DC%f--  5HHUOO"mE2""Fq AAFFq66F1I%% $ 0 0 7 7I 7SVVi%7%7o A o.6 #AF1I 1 KK     dw66777  C   !4!8!8!F!F      rcZ|r||S||Sr)rjustljust)valuewidthr9s r _add_paddingzTablePrinter._add_paddingVs.  &;;u%% %{{5!!!rchfdt|D}d|S)NcZg|]'\}}t||(Sr>)r+rV)rrNrT right_alignedrJs rr?z,TablePrinter._format_row..^sC   5  % %eVAY F F   rz )rBjoin)columnsrJrYcolss `` rrEzTablePrinter._format_row\sK     %g..   yyr)NNFN) __name__ __module__ __qualname__r3r;r'rr# staticmethodrVrEr>rrr+r+&s C C C C), 4""\" \rr+c||ndS)Nzn/ar>rTs rn_arces%5550rc(|t|n|Sr)intrbs rto_intrfis*3u:::5rcfd}|S)Nc\t|tr|S|Sr) isinstancedictr )rTr6s r extractorz extract_field..extractorns, eT " " $99U## # rr>)r6rks` r extract_fieldrlms$ rct}|D] }|j| |d|D|dS)Ncg|] }|d S)rr>)rrLs rr?zprint_table..zs111Ta111r)r+r;r#)r( field_propstablepropss r print_tablerrvsV NNE++""E*** KK11[111488888rcdtgfdtgfdtdgfdtgfdtgfdtgff}t||dS)N timestampabusercountrycodetimesnameseverity)rfrcrlrrr(ros rprint_incidentsr|}se vh C5 ]6**+, 3% # cU Kk"""""rcttj}|D],}|dd}|dkr ||z |d<'d|d<-dS)N expirationrttl)retimer )r(nowrLr~s radd_ttlrse dikk  CXXlA.. >>$s*DKKDKK rcnt|dddtdgff}t||dS)Niprrvrwrrlrrr{s rprint_graylistrsE DMMM ]6**+,K k"""""rcrt|dddtdgfddf}t||dS)Nrrrvrw) imported_from)commentrr{s r print_bwlistrsK DMMM ]6**+, Kk"""""rct|ttfrt|rt }t|dt rdt |d}|dtdg| ||dS|D]}t|dSdSt|dS)Nrrvrw)r7) rir.tuplerAr+rjsortedkeysr;rlr#)r(printerrrLs r guess_printerrs$u &&  t99 "nnG$q'4(( d1gllnn--,, f(=(='>- dD)))))   D$KKKK    d rct|trt|dSttj|ddS)NF)default_flow_style)rirCr#yamldumpr(s r yaml_printerrsF$9 d  di77788888rct|trt|dSttj|dSr)rirCr#jsondumpsrs r json_printerrsA$  d  djrc Vt|tr*td|ddSg}|D]B}|d|d|d|drdndCtd |dS) Nz Status: {}statuszEvent: {}, Path: {}{}eventrnativez native )rirjr#formatrDrZ)r(resulthooks r hook_printerrs$ ! l!!$x.1122222  D MM'..ML"&x.8JJb     dii     rc|stddSdddd}|D]}||dxxdz cc<tdjdi|tt|ddS) NzNo users targeted.r) succeededskippedfailedrz:{succeeded} succeeded, {skipped} skipped, {failed} failed.))user)r)reasonr>)r#rrr)rGcountsrLs rwaf_set_printerrs  "###a 8 8F$$tH~!# KDK      GGG<=====rczd|ddz}|dds|dz }t|td|ddz|d pg}|d t|}t||kr1td |t|n"td |t|std dSt |ddS)Nz Global WAF: global_wafunknownsecurity_plugin_enabledTz (plugin off)zDefault (no override): global_waf_defaultrG total_countzTotal accounts: {} (showing {})zTotal accounts: {}z No accounts.))ry) waf_status)source)wp_sites)r r#rArrr)rr:rGtotals rwaf_status_printerrs> fjjyAA AF ::/ 6 6"/! &MMM !FJJ/CY$O$OO JJw   %2E JJ}c%jj 1 1E 5zzE /66uc%jjIIJJJJ "))%00111 GGG  n @r)configshow)eularr ) whitelist)rrr.) blacklist)rrr.)graylist)rrr.)malwarez on-demandr)feature-managementdefaults)rr)renable)rdisable)rr )radd)rdelete))rr.)rz add-native)wordpress-pluginwafsetrrrrc@|d|dndS)NrGOKr)rs r_get_default_outputrs!$jj11=6'??4GrFcZ|rtni}ttj|fi|dSr)PRETTY_JSON_ARGSr#rr)r is_verbose pretty_argss r_print_json_responser#s8&08""bK $*V + +{ + +,,,,,rct|t}|tvr ||dS|t |dS)zrrrsJ######  !%+NN { #8%Iug6 Q  ! HHH<<<<<<<<~111666999 # # #### # # #"999    ! ! ! > > > 4   e  o L       L    >  '  '  #L %l &| "<  \! " # $#((7+=+   4??HHH---- ///....(((((16 '?Bz ' ' ' ' ' ' 'rdefence360agent/utils/__pycache__/common.cpython-311.opt-1.pyc0000644000000000000000000005300700000000000021054 0ustar r_j9NddlZddlZddlZddlZddlZddlZddlZddlZddlZej d Z ej d Z ej d Z ej d ZejeZGddeZGd d eZGd d ZeZGd dZeZdZGddZGddeZddZdS)N)minutes)hours)days)weeksceZdZdZdZdZdZdZdZdZ Gdd e Z Gd d e Z Gd d e Z Gdde ZdS) ServiceBasezBase service class.cd||_d|_d|_|||_dSNF)_loop _should_stop _main_task StoppedState_state)selfloops Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/common.py__init__zServiceBase.__init__s1 !''-- c4|jSN)rstartrs rrzServiceBase.starts{  """rc4|jSr)r should_stoprs rrzServiceBase.should_stops{&&(((rcDK|jd{VSr)rwaitrs rrzServiceBase.wait"s,[%%'''''''''rc4|jSr)r is_runningrs rrzServiceBase.is_running%s{%%'''rcKtr)NotImplementedErrorrs r_runzServiceBase._run(s !!rc,eZdZdZdZdZdZdZdS)ServiceBase.Statec||_dS)z:type obj: ServiceBaseN)_objrobjs rrzServiceBase.State.__init__,s DIIIrcdSrrs rrzServiceBase.State.start0 DrcdSrr*rs rrzServiceBase.State.should_stop3r+rc:K|jj}|r |d{VdSdSr)r&r)rtasks rrzServiceBase.State.wait6s79'D    rcdSr r*rs rrzServiceBase.State.is_running;s5rN)__name__ __module__ __qualname__rrrrrr*rrStater$+s_                  rr3ceZdZdZdZdS)ServiceBase.StoppedStatecpt|j|j_d|j_dSr )r rr&rr )rfutures r_on_stopz!ServiceBase.StoppedState._on_stop?s,*77 BBDI %*DI " " "rc|j}|j||_|j|jt||_ dSr) r&r create_taskr"radd_done_callbackr8r RunningStaterr's rrzServiceBase.StoppedState.startCsY)C Y22388::>>CN N , ,T] ; ; ;$11#66CJJJrN)r0r1r2r8rr*rrrr5>s2 + + + 7 7 7 7 7rrceZdZdZdZdS)ServiceBase.RunningStatec|j}d|_|jt||_dSNT)r&r rcancelr StoppingStaterr's rrz$ServiceBase.RunningState.should_stopJs>)C#C  N ! ! # # #$22377CJJJrcdSr@r*rs rrz#ServiceBase.RunningState.is_runningPs4rN)r0r1r2rrr*rrr<r>Is2 8 8 8      rr<ceZdZdZdS)ServiceBase.StoppingStatec td)Nz9Cannot start stopping service. Please wait while it stop.)ProgrammingErrorrs rrzServiceBase.StoppingState.startTs"K rN)r0r1r2rr*rrrBrESs#     rrBN)r0r1r2__doc__rrrrrr"objectr3rr<rBr*rrr r s... ###)))(((((("""& 7 7 7 7 7u 7 7 7urr ceZdZdS)rGN)r0r1r2r*rrrGrGZsDrrGcHeZdZdZejfdddZedZdZ dS) RateLimita3Decorator to limit function calls to one per *period* seconds. If less than *period* seconds have passed since the last call, then the request to call the function is replace with an *on_drop* call with the same arguments. If *on_drop* is None [default] then the call is just dropped N)on_dropc>d|_||_||_||_dSr)_next_call_time_period_timer_on_drop)rperiodtimerrMs rrzRateLimit.__init__is##   rcN|jdup|j|kSr)rOrQrs rshould_be_calledzRateLimit.should_be_calledos,  D ( 5#t{{}}4 rctjfd}tjfd}tjr|n|S)Ncjr)jz_|i|Sj j|i|SdSrrVrQrPrOrRargskwargsfuncrs rwrapperz#RateLimit.__call__..wrapperwsa$ 6'+{{}}t|'C$tT,V,,,*$t}d5f555+*rcKjr/jz_|i|d{VSj j|i|SdSrrYrZs r async_wrapperz)RateLimit.__call__..async_wrappersw$ 6'+{{}}t|'C$!T426222222222*$t}d5f555+*r) functoolswrapsasyncioiscoroutinefunction)rr]r^r`s`` r__call__zRateLimit.__call__vs    6 6 6 6 6   6    6 6 6 6 6   6!( ;D A AN}}wNr) r0r1r2rHtime monotonicrpropertyrVrer*rrrLrL^st&*^        X OOOOOrrLc eZdZdZdddZdS) CoalesceCallsc<td|_d|_dS)Nz-inf)float call_time delayed_callrs rrzCoalesceCalls.__init__sv rN) done_callbackcfd}|S)a Decorator to coalesce coroutine calls to one per *period* seconds. Requests for a coroutine call in a given time period are coalesced: If t is the time of the last call, then N call requests in the [t, t+period) time interval results in a single call at the t+period time iff N>0 i.e., if less than *period* seconds have passed since the last call, then the calls are coalesced: (N-1) requests are dropped, Nth requests is performed in *period* seconds. It is unspecified which exact call is made if arguments differ. If the call is not dropped then *done_callback* is attached to the task when the coroutine is scheduled with the event loop. Given `c` is the time of the last [actual] call (`loop.create_task()`) And `T` is the coalesce time period When a call request arrives at `t` time Then | call pending? | t>c+T | c<=t<=c+T | t.decorator..wrapper..log_exceptions}  >>++0@0@0L33+H"+,,-1^^-=-=(, 0L0Lrctd_d_||i|}|ndS)z*Call & schedule the delayed coroutine now.zSchedule call %sN)loggerinforfrmrnr:r;) coror[r\r.r|ror}rrs r call_delayedzVCoalesceCalls.coalesce_calls..decorator..wrapper..call_delayedsKK 2I>>>%)YY[[DN(,D%++DD$,A&,A,ABBD**(0& *rziThere was a scheduled call (%s) but more than period (%r) seconds passed since the last call (%r, now=%r)zSSatisfy the call request soon: %s. No calls in more than %r seconds since the startz`Drop call request for %s, enforcing one call per %r seconds limit. Next call is in ~%.2f secondszQDelay call request: %s for ~%.2f seconds. Enforcing one call per %r seconds limitzNDrop call request for %s, reason: last call time (%r, now=%r) is in the future)getrcget_event_loopr0rfrmrnstrrArwarningr call_sooncall_at)r[r\ args_reprrnowold_delayed_call_reprdelayr|r}rrrorSrs @@@rr^z@CoalesceCalls.coalesce_calls..decorator..wrapperszz&))<"133D#6#/3ttVV &"!  $0888 H%!" -1LL NV3(  " --)))NN9! r)rarb)rr^rorSrs` r decoratorz/CoalesceCalls.coalesce_calls..decoratorsQ _T " "c c c c c c c # "c JNrr*)rrSrors``` rcoalesce_callszCoalesceCalls.coalesce_callss88g g g g g g g Rr)r0r1r2rrr*rrrjrjsH!!!7;EEEEEEErrjctj}|'|drtjS|S)ziReturns readable name of the server. It is sent to CLN and allows user to sort out his servers. N localhost)socketgetfqdnlower startswith gethostname)hostnames r get_hostnamersH ~H8>>++66{CC!### Orc>eZdZdZd dZdZdZdZdZdZ d Z dS) VersionzAbstract base class for version numbering classes. Just provides constructor (__init__) and reproducer (__repr__), because those seem to be the same for all version numbering classes; and route rich comparisons to _cmp. Nc8|r||dSdSr)parse)rvstrings rrzVersion.__init__3s,  JJw       rc\d|jjt|S)Nz {} ('{}'))format __class__r0rrs r__repr__zVersion.__repr__7s#!!$."93t99EEErcN||}|tur|S|dkSNr_cmpNotImplementedrothercs r__eq__zVersion.__eq__:, IIe     HAv rcN||}|tur|S|dkSrrrs r__lt__zVersion.__lt__@, IIe     H1u rcN||}|tur|S|dkSrrrs r__le__zVersion.__le__FrrcN||}|tur|S|dkSrrrs r__gt__zVersion.__gt__LrrcN||}|tur|S|dkSrrrs r__ge__zVersion.__ge__Rrrr) r0r1r2rHrrrrrrrr*rrrr,s     FFF    rrcVeZdZdZejdejZdZdZ dZ dZ dS) LooseVersionaVersion numbering for anarchists and software realists. Implements the standard interface for version number classes as described above. A version number consists of a series of numbers, separated by either periods or strings of letters. When comparing version numbers, the numeric components will be compared numerically, and the alphabetic components lexically. The following are all valid version numbers, in no particular order: 1.5.1 1.5.2b2 161 3.10a 8.02 3.4j 1996.07.12 3.2.pl0 3.1.1.6 2g6 11g 0.960923 2.2beta29 1.13++ 5.5.kw 2.0b1pl0 In fact, there is no such thing as an invalid version number under this scheme; the rules for comparison are simple and predictable, but may not always give the results you want (for some definition of "want"). z(\d+ | [a-z]+ | \.)c||_d|j|D}t|D](\}} t |||<#t $rY%wxYw||_dS)Nc"g|] }|r|dk | S).r*).0xs r z&LooseVersion.parse..s,   1 ABcAr)r component_resplit enumerateint ValueErrorversion)rr componentsir(s rrzLooseVersion.parse}s   (..w77    ++  FAs  #C 1     " sA A! A!c|jSr)rrs r__str__zLooseVersion.__str__s |rc&dt|zS)NzLooseVersion ('%s'))rrs rrzLooseVersion.__repr__s$s4yy00rct|trt|}nt|tstS|j|jkrdS|j|jkrdS|j|jkrdSdS)Nrr) isinstancerrrr)rrs rrzLooseVersion._cmps eS ! ! " ''EEE<00 "! ! <5= ( (1 <%- ' '2 <%- ' '1 ( 'rN) r0r1r2rHrecompileVERBOSErrrrrr*rrrrZsr>2:4bjAAL""" 111     rrcRtj|\}}tjdkr |dkr|dz}tj|r|S|[tjdd}|9 tjd}n##ttf$rtj }YnwxYw|sdS| tj }|D]E}tj||}tj|r|cSFdS)zTries to find 'executable' in the directories listed in 'path'. A string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']. Returns the complete filename or None if not found. win32z.exeNPATHCS_PATH)ospathsplitextsysplatformisfileenvironrconfstrAttributeErrorrdefpathrpathsepjoin) executabler_extpathspfs rfind_executablers) W  j ) )FAs cVmm&(  w~~j!! |z~~fd++ < "z),,"J/ " " "z " t JJrz " "E  GLLJ ' ' 7>>!   HHH  4sBB98B9r)rcdatetimeraloggingrrfrrr timedelta total_secondsMINUTEHOURDAYWEEK getLoggerr0rrIr ExceptionrGrL rate_limitrjwebserver_gracefull_restartrrrrr*rrrsL  A & & & 4 4 6 6x"""0022ha   ..00x"""0022  8 $ $DDDDD&DDDN     y   )O)O)O)O)O)O)O)OX JJJJJJJJZ,moo********\DDDDD7DDDP""""""rdefence360agent/utils/__pycache__/common.cpython-311.pyc0000644000000000000000000005300700000000000020115 0ustar r_j9NddlZddlZddlZddlZddlZddlZddlZddlZddlZej d Z ej d Z ej d Z ej d ZejeZGddeZGd d eZGd d ZeZGd dZeZdZGddZGddeZddZdS)N)minutes)hours)days)weeksceZdZdZdZdZdZdZdZdZ Gdd e Z Gd d e Z Gd d e Z Gdde ZdS) ServiceBasezBase service class.cd||_d|_d|_|||_dSNF)_loop _should_stop _main_task StoppedState_state)selfloops Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/common.py__init__zServiceBase.__init__s1 !''-- c4|jSN)rstartrs rrzServiceBase.starts{  """rc4|jSr)r should_stoprs rrzServiceBase.should_stops{&&(((rcDK|jd{VSr)rwaitrs rrzServiceBase.wait"s,[%%'''''''''rc4|jSr)r is_runningrs rrzServiceBase.is_running%s{%%'''rcKtr)NotImplementedErrorrs r_runzServiceBase._run(s !!rc,eZdZdZdZdZdZdZdS)ServiceBase.Statec||_dS)z:type obj: ServiceBaseN)_objrobjs rrzServiceBase.State.__init__,s DIIIrcdSrrs rrzServiceBase.State.start0 DrcdSrr*rs rrzServiceBase.State.should_stop3r+rc:K|jj}|r |d{VdSdSr)r&r)rtasks rrzServiceBase.State.wait6s79'D    rcdSr r*rs rrzServiceBase.State.is_running;s5rN)__name__ __module__ __qualname__rrrrrr*rrStater$+s_                  rr3ceZdZdZdZdS)ServiceBase.StoppedStatecpt|j|j_d|j_dSr )r rr&rr )rfutures r_on_stopz!ServiceBase.StoppedState._on_stop?s,*77 BBDI %*DI " " "rc|j}|j||_|j|jt||_ dSr) r&r create_taskr"radd_done_callbackr8r RunningStaterr's rrzServiceBase.StoppedState.startCsY)C Y22388::>>CN N , ,T] ; ; ;$11#66CJJJrN)r0r1r2r8rr*rrrr5>s2 + + + 7 7 7 7 7rrceZdZdZdZdS)ServiceBase.RunningStatec|j}d|_|jt||_dSNT)r&r rcancelr StoppingStaterr's rrz$ServiceBase.RunningState.should_stopJs>)C#C  N ! ! # # #$22377CJJJrcdSr@r*rs rrz#ServiceBase.RunningState.is_runningPs4rN)r0r1r2rrr*rrr<r>Is2 8 8 8      rr<ceZdZdZdS)ServiceBase.StoppingStatec td)Nz9Cannot start stopping service. Please wait while it stop.)ProgrammingErrorrs rrzServiceBase.StoppingState.startTs"K rN)r0r1r2rr*rrrBrESs#     rrBN)r0r1r2__doc__rrrrrr"objectr3rr<rBr*rrr r s... ###)))(((((("""& 7 7 7 7 7u 7 7 7urr ceZdZdS)rGN)r0r1r2r*rrrGrGZsDrrGcHeZdZdZejfdddZedZdZ dS) RateLimita3Decorator to limit function calls to one per *period* seconds. If less than *period* seconds have passed since the last call, then the request to call the function is replace with an *on_drop* call with the same arguments. If *on_drop* is None [default] then the call is just dropped N)on_dropc>d|_||_||_||_dSr)_next_call_time_period_timer_on_drop)rperiodtimerrMs rrzRateLimit.__init__is##   rcN|jdup|j|kSr)rOrQrs rshould_be_calledzRateLimit.should_be_calledos,  D ( 5#t{{}}4 rctjfd}tjfd}tjr|n|S)Ncjr)jz_|i|Sj j|i|SdSrrVrQrPrOrRargskwargsfuncrs rwrapperz#RateLimit.__call__..wrapperwsa$ 6'+{{}}t|'C$tT,V,,,*$t}d5f555+*rcKjr/jz_|i|d{VSj j|i|SdSrrYrZs r async_wrapperz)RateLimit.__call__..async_wrappersw$ 6'+{{}}t|'C$!T426222222222*$t}d5f555+*r) functoolswrapsasyncioiscoroutinefunction)rr]r^r`s`` r__call__zRateLimit.__call__vs    6 6 6 6 6   6    6 6 6 6 6   6!( ;D A AN}}wNr) r0r1r2rHtime monotonicrpropertyrVrer*rrrLrL^st&*^        X OOOOOrrLc eZdZdZdddZdS) CoalesceCallsc<td|_d|_dS)Nz-inf)float call_time delayed_callrs rrzCoalesceCalls.__init__sv rN) done_callbackcfd}|S)a Decorator to coalesce coroutine calls to one per *period* seconds. Requests for a coroutine call in a given time period are coalesced: If t is the time of the last call, then N call requests in the [t, t+period) time interval results in a single call at the t+period time iff N>0 i.e., if less than *period* seconds have passed since the last call, then the calls are coalesced: (N-1) requests are dropped, Nth requests is performed in *period* seconds. It is unspecified which exact call is made if arguments differ. If the call is not dropped then *done_callback* is attached to the task when the coroutine is scheduled with the event loop. Given `c` is the time of the last [actual] call (`loop.create_task()`) And `T` is the coalesce time period When a call request arrives at `t` time Then | call pending? | t>c+T | c<=t<=c+T | t.decorator..wrapper..log_exceptions}  >>++0@0@0L33+H"+,,-1^^-=-=(, 0L0Lrctd_d_||i|}|ndS)z*Call & schedule the delayed coroutine now.zSchedule call %sN)loggerinforfrmrnr:r;) coror[r\r.r|ror}rrs r call_delayedzVCoalesceCalls.coalesce_calls..decorator..wrapper..call_delayedsKK 2I>>>%)YY[[DN(,D%++DD$,A&,A,ABBD**(0& *rziThere was a scheduled call (%s) but more than period (%r) seconds passed since the last call (%r, now=%r)zSSatisfy the call request soon: %s. No calls in more than %r seconds since the startz`Drop call request for %s, enforcing one call per %r seconds limit. Next call is in ~%.2f secondszQDelay call request: %s for ~%.2f seconds. Enforcing one call per %r seconds limitzNDrop call request for %s, reason: last call time (%r, now=%r) is in the future)getrcget_event_loopr0rfrmrnstrrArwarningr call_sooncall_at)r[r\ args_reprrnowold_delayed_call_reprdelayr|r}rrrorSrs @@@rr^z@CoalesceCalls.coalesce_calls..decorator..wrapperszz&))<"133D#6#/3ttVV &"!  $0888 H%!" -1LL NV3(  " --)))NN9! r)rarb)rr^rorSrs` r decoratorz/CoalesceCalls.coalesce_calls..decoratorsQ _T " "c c c c c c c # "c JNrr*)rrSrors``` rcoalesce_callszCoalesceCalls.coalesce_callss88g g g g g g g Rr)r0r1r2rrr*rrrjrjsH!!!7;EEEEEEErrjctj}|'|drtjS|S)ziReturns readable name of the server. It is sent to CLN and allows user to sort out his servers. N localhost)socketgetfqdnlower startswith gethostname)hostnames r get_hostnamersH ~H8>>++66{CC!### Orc>eZdZdZd dZdZdZdZdZdZ d Z dS) VersionzAbstract base class for version numbering classes. Just provides constructor (__init__) and reproducer (__repr__), because those seem to be the same for all version numbering classes; and route rich comparisons to _cmp. Nc8|r||dSdSr)parse)rvstrings rrzVersion.__init__3s,  JJw       rc\d|jjt|S)Nz {} ('{}'))format __class__r0rrs r__repr__zVersion.__repr__7s#!!$."93t99EEErcN||}|tur|S|dkSNr_cmpNotImplementedrothercs r__eq__zVersion.__eq__:, IIe     HAv rcN||}|tur|S|dkSrrrs r__lt__zVersion.__lt__@, IIe     H1u rcN||}|tur|S|dkSrrrs r__le__zVersion.__le__FrrcN||}|tur|S|dkSrrrs r__gt__zVersion.__gt__LrrcN||}|tur|S|dkSrrrs r__ge__zVersion.__ge__Rrrr) r0r1r2rHrrrrrrrr*rrrr,s     FFF    rrcVeZdZdZejdejZdZdZ dZ dZ dS) LooseVersionaVersion numbering for anarchists and software realists. Implements the standard interface for version number classes as described above. A version number consists of a series of numbers, separated by either periods or strings of letters. When comparing version numbers, the numeric components will be compared numerically, and the alphabetic components lexically. The following are all valid version numbers, in no particular order: 1.5.1 1.5.2b2 161 3.10a 8.02 3.4j 1996.07.12 3.2.pl0 3.1.1.6 2g6 11g 0.960923 2.2beta29 1.13++ 5.5.kw 2.0b1pl0 In fact, there is no such thing as an invalid version number under this scheme; the rules for comparison are simple and predictable, but may not always give the results you want (for some definition of "want"). z(\d+ | [a-z]+ | \.)c||_d|j|D}t|D](\}} t |||<#t $rY%wxYw||_dS)Nc"g|] }|r|dk | S).r*).0xs r z&LooseVersion.parse..s,   1 ABcAr)r component_resplit enumerateint ValueErrorversion)rr componentsir(s rrzLooseVersion.parse}s   (..w77    ++  FAs  #C 1     " sA A! A!c|jSr)rrs r__str__zLooseVersion.__str__s |rc&dt|zS)NzLooseVersion ('%s'))rrs rrzLooseVersion.__repr__s$s4yy00rct|trt|}nt|tstS|j|jkrdS|j|jkrdS|j|jkrdSdS)Nrr) isinstancerrrr)rrs rrzLooseVersion._cmps eS ! ! " ''EEE<00 "! ! <5= ( (1 <%- ' '2 <%- ' '1 ( 'rN) r0r1r2rHrecompileVERBOSErrrrrr*rrrrZsr>2:4bjAAL""" 111     rrcRtj|\}}tjdkr |dkr|dz}tj|r|S|[tjdd}|9 tjd}n##ttf$rtj }YnwxYw|sdS| tj }|D]E}tj||}tj|r|cSFdS)zTries to find 'executable' in the directories listed in 'path'. A string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']. Returns the complete filename or None if not found. win32z.exeNPATHCS_PATH)ospathsplitextsysplatformisfileenvironrconfstrAttributeErrorrdefpathrpathsepjoin) executabler_extpathspfs rfind_executablers) W  j ) )FAs cVmm&(  w~~j!! |z~~fd++ < "z),,"J/ " " "z " t JJrz " "E  GLLJ ' ' 7>>!   HHH  4sBB98B9r)rcdatetimeraloggingrrfrrr timedelta total_secondsMINUTEHOURDAYWEEK getLoggerr0rrIr ExceptionrGrL rate_limitrjwebserver_gracefull_restartrrrrr*rrrsL  A & & & 4 4 6 6x"""0022ha   ..00x"""0022  8 $ $DDDDD&DDDN     y   )O)O)O)O)O)O)O)OX JJJJJJJJZ,moo********\DDDDD7DDDP""""""rdefence360agent/utils/__pycache__/completions.cpython-311.opt-1.pyc0000644000000000000000000004100100000000000022107 0ustar r_j' 2dZddlZddlZddlmZmZmZdedefdZdej deeedfeeffd Z dej deefd Z dej d eedfd eeedfeeffd Z deeedfeefdeedfdeefdZ ddej dedefdZ ddej dedefdZ ddej dedefdZeeedZeeZ ddej dededefdZdS)z Shell auto-completion script generators for the CLI. Introspects an argparse parser to enumerate all commands, subcommands, and flags, then emits completion scripts for bash, zsh, and fish. N)DictListTupleprogreturnc.tjdd|S)zDConvert a prog name to a safe shell identifier (letters, digits, _).z [^a-zA-Z0-9]_)resub)rs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/completions.py_safe_identifierr s 6/3 - --parser.c,i}t|d||S)z@Walk the parser tree and return {command_path: [flags]} mapping.) _walk_parser)rresults r _collect_commandsrs 02FV$$$ Mrcg}|jD]W}t|tjrt|tjr8|jD]}||Xt|S)z:Extract all optional flags from a parser (excluding help).)_actions isinstanceargparse _HelpAction_SubParsersActionoption_stringsappendsorted)rflagsactionopts r _get_flagsr!s E/ fh2 3 3   fh8 9 9  (  C LL      %==rpathrct|}|||<|jD]P}t|tjr4|jD]\}}t|||fz|QdS)z>Recursively walk subparsers and collect command paths + flags.N)r!rrrrchoicesitemsr)rr"rrrname subparsers r rr(s v  EF4L/@@ fh8 9 9 @#)>#7#7#9#9 @ @iYw????@@rcommandsprefixct}|D][}t|t|dzkr6|dt||kr||d\t|S)z,Get immediate subcommands of a given prefix.N)setlenaddr)r(r)subsr"s r _get_subcommandsr17ss 55D t99F a ' 'D3v;;,?6,I,I HHT"X    $<<rimunify360-agentc,t|}g}|d||d|d|d|dt|d|d|d|d |d |d |d |d |d|d|d|d|d|d|d|d|d|d|d|d|d|d|dt|d}|D]}|st ||}||}d||z}d|} |d| d|d |d!|d"t |d#} |d#g} d| | z} |d$|d | d!|d"|d%|d&|d|d't|d(||dd)|S)*z"Generate a bash completion script.z# bash completion for # Auto-generated by z completions bashr z_completions() {z local cur prev words cwordz. if type _init_completion &>/dev/null; thenz" _init_completion || returnz elsez COMPREPLY=()z' cur="${COMP_WORDS[COMP_CWORD]}"z* prev="${COMP_WORDS[COMP_CWORD-1]}"z" words=("${COMP_WORDS[@]}")z cword=$COMP_CWORDz fiz' # Build the command path from wordsz local cmd_path=""z local iz% for (( i=1; i < cword; i++ )); doz case "${words[i]}" inz -*) continue ;;zB *) cmd_path="${cmd_path:+${cmd_path} }${words[i]}" ;;z esac donez case "$cmd_path" inc&t| |fSNr.ps r zgenerate_bash..gAwlrkey "")z% COMPREPLY=($(compgen -W "z " -- "$cur"))z return ;;r "") esac}z complete -F _z _completions  )rrr rkeysr1joinget) rrr(lines all_pathsr"r0r completionspattern root_subs root_flagsroot_completionss r generate_bashrQCs!((H E LL0$00111 LL????@@@ LL LL>%d++>>>??? LL1222 LLABBB LL5666 LL LL'((( LL:;;; LL=>>> LL5666 LL,--- LL LL LL:;;; LL())) LL LL8999 LL0111 LL./// LLL LL    LL LL LL*+++x}},B,BCCCI . .  $//hhte|,, ((4.. ,,,,--- NK N N N     ,----!2..Ib"%%Jxx J 677 LL LLO0@OOO LL())) LL LL LL LLL!1$!7!7LLdLLMMM LL 99U  rc2t|}dt|}g}|d||d||d|d|d||d|d|d |d|d |d |d |d |d|d|d|d|dt|d}|D]}|st ||}||}d|} |d| d|r8dd|D} |d| d|r8dd|D} |d| d||rdndd|d|dt |d } |d g} dd!| D}|d"|d|d| r8dd#| D} |d| d|d|d|d$|d%|d|||dd&|S)'z!Generate a zsh completion script.r z #compdef z# zsh completion for r4z completions zshr5z() {z local -a commands flagsz local cmd_pathz# # Build command path from wordsz cmd_path=()z" for word in ${words[2,-1]}; doz% [[ $word == -* ]] && continuez5 [[ $word == "$words[$CURRENT]" ]] && continuez cmd_path+=($word)r6z case "${cmd_path[*]}" inc&t| |fSr8r9r:s r r<zgenerate_zsh..r=rr>r@rArBc3"K|] }d|dV dS"Nr.0ss r zgenerate_zsh..s* 8 8aQ 8 8 8 8 8 8rz commands=()c3"K|] }d|dV dSrUrrXfs r rZzgenerate_zsh..s* 9 9aQ 9 9 9 9 9 9rz flags=(z; _describe 'command' commands -- flags && returnz compadd -- z && returnz ;;rc3"K|] }d|dV dSrUrrWs r rZzgenerate_zsh..s*55aQ555555rrCc3"K|] }d|dV dSrUrr]s r rZzgenerate_zsh..s*::!XXXX::::::rrDrErF)rr rrrGr1rHrI)rrr( func_namerJrKr"r0rrM desc_list flag_listrNrO root_descs r generate_zshres;!((H,$T**,,I E LL#T##$$$ LL///000 LL>>>>??? LL LLI$$$%%% LL./// LL%&&& LL LL6777 LL"### LL5666 LL8999 LLHIII LL,--- LL LL LL/000x}},B,BCCCI''  $//((4.. ,,,,---  @ 8 84 8 8 888I LL>)>>> ? ? ?  = 9 95 9 9 999I LL;y;;; < < <  G I IF388E??FFF    %&&&&!2..Ib"%%J55955555I LL LL6)6667779HH::z:::::  79777888 LLNOOO LL!""" LL LL LL LLI    LL 99U  rc t|}g}|d||d|d|dt|dD]}t ||}||}|sddt |d }|D]!}|d |d |d |d "|D]{} | dr(|d |d |d| ddd ?| dr'|d |d |d| ddd |g} |D]} | d| d| }|} | r|dd| z }|D]!}|d |d |d |d "|D]{} | dr(|d |d |d| ddd ?| dr'|d |d |d| ddd ||dd|S)z"Generate a fish completion script.z# fish completion for r4z completions fishr5c$t||fSr8r9r:s r r<zgenerate_fish..ss1vvqkrr>z not __fish_seen_subcommand_from r@rz complete -c z -n 'z ' -f -a ''z--z' -l 'N-z' -s 'r+z__fish_seen_subcommand_from z && z$ && not __fish_seen_subcommand_from rF)rrrrGr1rH startswith) rrr(rJr"r0r conditionr flag seen_partsr; child_subss r generate_fishrps!((H E LL0$00111 LL????@@@ LLx}},A,ABBB11$//- ?HH-h;;<<??    H4HHiHH#HHH  ??4((LLNtNN)NN48NNN__S))LLNtNN)NN48NNN  J F F!!"D"D"DEEEE J//IJ /,,//    H4HHiHH#HHH  ??4((LLNtNN)NN48NNN__S))LLNtNN)NN48NNN   LL 99U  r)bashzshfishshellct|}|-td|ddt|||S)zfGenerate completion script for the given shell. Raises ValueError if shell is not supported. NzUnsupported shell: z. Supported shells: z, ) GENERATORSrI ValueErrorrHSUPPORTED_SHELLS)rrtr generators r generate_completionsrzslu%%I ?% ? ?!%+;!rs $$$$$$$$$$.3.3....   # %S/49 $% x. 49     @  # @ S/ @ sCx$s)+ , @ @ @ @ 5c?DI-.  #s(O  #Y    2D@@  #@+.@@@@@H2D>>  #>+.>>>>>D2D@@  #@+.@@@@@H    6*//++,, ###  ## # # ######rdefence360agent/utils/__pycache__/completions.cpython-311.pyc0000644000000000000000000004100100000000000021150 0ustar r_j' 2dZddlZddlZddlmZmZmZdedefdZdej deeedfeeffd Z dej deefd Z dej d eedfd eeedfeeffd Z deeedfeefdeedfdeefdZ ddej dedefdZ ddej dedefdZ ddej dedefdZeeedZeeZ ddej dededefdZdS)z Shell auto-completion script generators for the CLI. Introspects an argparse parser to enumerate all commands, subcommands, and flags, then emits completion scripts for bash, zsh, and fish. N)DictListTupleprogreturnc.tjdd|S)zDConvert a prog name to a safe shell identifier (letters, digits, _).z [^a-zA-Z0-9]_)resub)rs V/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/completions.py_safe_identifierr s 6/3 - --parser.c,i}t|d||S)z@Walk the parser tree and return {command_path: [flags]} mapping.) _walk_parser)rresults r _collect_commandsrs 02FV$$$ Mrcg}|jD]W}t|tjrt|tjr8|jD]}||Xt|S)z:Extract all optional flags from a parser (excluding help).)_actions isinstanceargparse _HelpAction_SubParsersActionoption_stringsappendsorted)rflagsactionopts r _get_flagsr!s E/ fh2 3 3   fh8 9 9  (  C LL      %==rpathrct|}|||<|jD]P}t|tjr4|jD]\}}t|||fz|QdS)z>Recursively walk subparsers and collect command paths + flags.N)r!rrrrchoicesitemsr)rr"rrrname subparsers r rr(s v  EF4L/@@ fh8 9 9 @#)>#7#7#9#9 @ @iYw????@@rcommandsprefixct}|D][}t|t|dzkr6|dt||kr||d\t|S)z,Get immediate subcommands of a given prefix.N)setlenaddr)r(r)subsr"s r _get_subcommandsr17ss 55D t99F a ' 'D3v;;,?6,I,I HHT"X    $<<rimunify360-agentc,t|}g}|d||d|d|d|dt|d|d|d|d |d |d |d |d |d|d|d|d|d|d|d|d|d|d|d|d|d|d|dt|d}|D]}|st ||}||}d||z}d|} |d| d|d |d!|d"t |d#} |d#g} d| | z} |d$|d | d!|d"|d%|d&|d|d't|d(||dd)|S)*z"Generate a bash completion script.z# bash completion for # Auto-generated by z completions bashr z_completions() {z local cur prev words cwordz. if type _init_completion &>/dev/null; thenz" _init_completion || returnz elsez COMPREPLY=()z' cur="${COMP_WORDS[COMP_CWORD]}"z* prev="${COMP_WORDS[COMP_CWORD-1]}"z" words=("${COMP_WORDS[@]}")z cword=$COMP_CWORDz fiz' # Build the command path from wordsz local cmd_path=""z local iz% for (( i=1; i < cword; i++ )); doz case "${words[i]}" inz -*) continue ;;zB *) cmd_path="${cmd_path:+${cmd_path} }${words[i]}" ;;z esac donez case "$cmd_path" inc&t| |fSNr.ps r zgenerate_bash..gAwlrkey "")z% COMPREPLY=($(compgen -W "z " -- "$cur"))z return ;;r "") esac}z complete -F _z _completions  )rrr rkeysr1joinget) rrr(lines all_pathsr"r0r completionspattern root_subs root_flagsroot_completionss r generate_bashrQCs!((H E LL0$00111 LL????@@@ LL LL>%d++>>>??? LL1222 LLABBB LL5666 LL LL'((( LL:;;; LL=>>> LL5666 LL,--- LL LL LL:;;; LL())) LL LL8999 LL0111 LL./// LLL LL    LL LL LL*+++x}},B,BCCCI . .  $//hhte|,, ((4.. ,,,,--- NK N N N     ,----!2..Ib"%%Jxx J 677 LL LLO0@OOO LL())) LL LL LL LLL!1$!7!7LLdLLMMM LL 99U  rc2t|}dt|}g}|d||d||d|d|d||d|d|d |d|d |d |d |d |d|d|d|d|dt|d}|D]}|st ||}||}d|} |d| d|r8dd|D} |d| d|r8dd|D} |d| d||rdndd|d|dt |d } |d g} dd!| D}|d"|d|d| r8dd#| D} |d| d|d|d|d$|d%|d|||dd&|S)'z!Generate a zsh completion script.r z #compdef z# zsh completion for r4z completions zshr5z() {z local -a commands flagsz local cmd_pathz# # Build command path from wordsz cmd_path=()z" for word in ${words[2,-1]}; doz% [[ $word == -* ]] && continuez5 [[ $word == "$words[$CURRENT]" ]] && continuez cmd_path+=($word)r6z case "${cmd_path[*]}" inc&t| |fSr8r9r:s r r<zgenerate_zsh..r=rr>r@rArBc3"K|] }d|dV dS"Nr.0ss r zgenerate_zsh..s* 8 8aQ 8 8 8 8 8 8rz commands=()c3"K|] }d|dV dSrUrrXfs r rZzgenerate_zsh..s* 9 9aQ 9 9 9 9 9 9rz flags=(z; _describe 'command' commands -- flags && returnz compadd -- z && returnz ;;rc3"K|] }d|dV dSrUrrWs r rZzgenerate_zsh..s*55aQ555555rrCc3"K|] }d|dV dSrUrr]s r rZzgenerate_zsh..s*::!XXXX::::::rrDrErF)rr rrrGr1rHrI)rrr( func_namerJrKr"r0rrM desc_list flag_listrNrO root_descs r generate_zshres;!((H,$T**,,I E LL#T##$$$ LL///000 LL>>>>??? LL LLI$$$%%% LL./// LL%&&& LL LL6777 LL"### LL5666 LL8999 LLHIII LL,--- LL LL LL/000x}},B,BCCCI''  $//((4.. ,,,,---  @ 8 84 8 8 888I LL>)>>> ? ? ?  = 9 95 9 9 999I LL;y;;; < < <  G I IF388E??FFF    %&&&&!2..Ib"%%J55955555I LL LL6)6667779HH::z:::::  79777888 LLNOOO LL!""" LL LL LL LLI    LL 99U  rc t|}g}|d||d|d|dt|dD]}t ||}||}|sddt |d }|D]!}|d |d |d |d "|D]{} | dr(|d |d |d| ddd ?| dr'|d |d |d| ddd |g} |D]} | d| d| }|} | r|dd| z }|D]!}|d |d |d |d "|D]{} | dr(|d |d |d| ddd ?| dr'|d |d |d| ddd ||dd|S)z"Generate a fish completion script.z# fish completion for r4z completions fishr5c$t||fSr8r9r:s r r<zgenerate_fish..ss1vvqkrr>z not __fish_seen_subcommand_from r@rz complete -c z -n 'z ' -f -a ''z--z' -l 'N-z' -s 'r+z__fish_seen_subcommand_from z && z$ && not __fish_seen_subcommand_from rF)rrrrGr1rH startswith) rrr(rJr"r0r conditionr flag seen_partsr; child_subss r generate_fishrps!((H E LL0$00111 LL????@@@ LLx}},A,ABBB11$//- ?HH-h;;<<??    H4HHiHH#HHH  ??4((LLNtNN)NN48NNN__S))LLNtNN)NN48NNN  J F F!!"D"D"DEEEE J//IJ /,,//    H4HHiHH#HHH  ??4((LLNtNN)NN48NNN__S))LLNtNN)NN48NNN   LL 99U  r)bashzshfishshellct|}|-td|ddt|||S)zfGenerate completion script for the given shell. Raises ValueError if shell is not supported. NzUnsupported shell: z. Supported shells: z, ) GENERATORSrI ValueErrorrHSUPPORTED_SHELLS)rrtr generators r generate_completionsrzslu%%I ?% ? ?!%+;!rs $$$$$$$$$$.3.3....   # %S/49 $% x. 49     @  # @ S/ @ sCx$s)+ , @ @ @ @ 5c?DI-.  #s(O  #Y    2D@@  #@+.@@@@@H2D>>  #>+.>>>>>D2D@@  #@+.@@@@@H    6*//++,, ###  ## # # ######rdefence360agent/utils/__pycache__/config.cpython-311.opt-1.pyc0000644000000000000000000000615200000000000021030 0ustar r_jddlZddlZddlZddlmZddlmZmZddlm Z ej j dz Z ee ZdZdZdZd Zd d ZdS) N) getLogger)configmessages)checkers KERNELCAREedfct|ttvrtddSdS)NzYConfiguration update with an obsolete kernelcare option 'edf'. This option has no effect.)OBSOLETE_OPTIONgetOBSOLETE_SECTIONdictloggerwarning)datas Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/config.pywarn_obsolete_optionrsM$((#3TVV<<<< *     =<c||d}t|tsdStjtjjkrX|ddurCtjjs4| dd|s| dddSdSdSdSdS)N WORDPRESS waf_enabledT) r isinstancerr caller_typeUserTypeNON_ROOT Wordpress WAF_DEFAULTpop)r wordpresss renforce_waf_optin_policyr s%%I i & &  FO$<<< MM- ( (D 0 0 , 1  mT*** ( HH[$ ' ' ' ' ' =< 0 0 0 0 ( (rc Kt|tj||t|t j|}||dtj}| tj |tj |tj|d{Vtj|t"d{VdS)NT)without_defaults)conf timestampevent submitted)timeout)rrconfig_validationr r ConfigFiledict_to_configasyncioEventprocess_messager ConfigUpdatetimecopydeepcopywait_forwaitCONFIG_UPDATE_TIMEOUT)sinkruserr#updateds r update_configr8's  tT***T"""  T " "Dt444mooG   ikkmD))               7<<>>3H I I IIIIIIIIIIr)N)r+r0r/loggingrdefence360agent.contractsrr"defence360agent.feature_managementr SimpleRpcCLIENT_TIMEOUTr4__name__rr r rr r8rrr@s 66666666777777(7!; 8      ( ( (JJJJJJrdefence360agent/utils/__pycache__/config.cpython-311.pyc0000644000000000000000000000615200000000000020071 0ustar r_jddlZddlZddlZddlmZddlmZmZddlm Z ej j dz Z ee ZdZdZdZd Zd d ZdS) N) getLogger)configmessages)checkers KERNELCAREedfct|ttvrtddSdS)NzYConfiguration update with an obsolete kernelcare option 'edf'. This option has no effect.)OBSOLETE_OPTIONgetOBSOLETE_SECTIONdictloggerwarning)datas Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/config.pywarn_obsolete_optionrsM$((#3TVV<<<< *     =<c||d}t|tsdStjtjjkrX|ddurCtjjs4| dd|s| dddSdSdSdSdS)N WORDPRESS waf_enabledT) r isinstancerr caller_typeUserTypeNON_ROOT Wordpress WAF_DEFAULTpop)r wordpresss renforce_waf_optin_policyr s%%I i & &  FO$<<< MM- ( (D 0 0 , 1  mT*** ( HH[$ ' ' ' ' ' =< 0 0 0 0 ( (rc Kt|tj||t|t j|}||dtj}| tj |tj |tj|d{Vtj|t"d{VdS)NT)without_defaults)conf timestampevent submitted)timeout)rrconfig_validationr r ConfigFiledict_to_configasyncioEventprocess_messager ConfigUpdatetimecopydeepcopywait_forwaitCONFIG_UPDATE_TIMEOUT)sinkruserr#updateds r update_configr8's  tT***T"""  T " "Dt444mooG   ikkmD))               7<<>>3H I I IIIIIIIIIIr)N)r+r0r/loggingrdefence360agent.contractsrr"defence360agent.feature_managementr SimpleRpcCLIENT_TIMEOUTr4__name__rr r rr r8rrr@s 66666666777777(7!; 8      ( ( (JJJJJJrdefence360agent/utils/__pycache__/cronjob.cpython-311.opt-1.pyc0000644000000000000000000000406600000000000021221 0ustar r_j2ddlmZmZGddeZdS))UnionOptionalcreZdZdZdeeedfdeeedfdeefdZdZ e dZ dS) CronJobminutehourcmdrNr r c0||_||_||_dS)Nr)selfrr r s R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cronjob.py__init__zCronJob.__init__s  c8d|jd|jd|jdS)Nz6# DO NOT EDIT. AUTOMATICALLY GENERATED BY IMUNIFY360.  z * * * root  r)r s r __str__zCronJob.__str__sF C C C#y C C6:h C C C rcdx}x}}d|D}|rH|dd}|d}|d}d|dd}t|||S)Nc*g|]}|ddk|S)r#).0xs r z$CronJob.from_str..s!===q1rrrr) splitlinessplitjoinr)clsdatarr r lines line_memberss r from_strzCronJob.from_strs"""==DOO--===  - 8>>#..L!!_F?D((<+,,Cf4S9999r) __name__ __module__ __qualname__ __slots__rintstrrrr classmethodr$rrr rrs'I c3n% CdN#  c]       ::[:::rrN)typingrrobjectrrrr r.sQ"""""""":::::f:::::rdefence360agent/utils/__pycache__/cronjob.cpython-311.pyc0000644000000000000000000000406600000000000020262 0ustar r_j2ddlmZmZGddeZdS))UnionOptionalcreZdZdZdeeedfdeeedfdeefdZdZ e dZ dS) CronJobminutehourcmdrNr r c0||_||_||_dS)Nr)selfrr r s R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/cronjob.py__init__zCronJob.__init__s  c8d|jd|jd|jdS)Nz6# DO NOT EDIT. AUTOMATICALLY GENERATED BY IMUNIFY360.  z * * * root  r)r s r __str__zCronJob.__str__sF C C C#y C C6:h C C C rcdx}x}}d|D}|rH|dd}|d}|d}d|dd}t|||S)Nc*g|]}|ddk|S)r#).0xs r z$CronJob.from_str..s!===q1rrrr) splitlinessplitjoinr)clsdatarr r lines line_memberss r from_strzCronJob.from_strs"""==DOO--===  - 8>>#..L!!_F?D((<+,,Cf4S9999r) __name__ __module__ __qualname__ __slots__rintstrrrr classmethodr$rrr rrs'I c3n% CdN#  c]       ::[:::rrN)typingrrobjectrrrr r.sQ"""""""":::::f:::::rdefence360agent/utils/__pycache__/doctor.cpython-311.opt-1.pyc0000644000000000000000000002424300000000000021056 0ustar r_jddlZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZmZdZejeZdZd ezZed zZe d Ze d e d fZde e fdZdede ddfdZdZde ddfdZ de de!ddfdZ"dede ddfdZ#de e fdZ$defdZ%defdZ&dZ'dS)N)Path)Optional) Packaging) save_state) CheckRunError check_runzimunify-doctor.shz2https://repo.imunify360.cloudlinux.com/defence360/.sigz/var/imunify360/tmpz//etc/pki/rpm-gpg/RPM-GPG-KEY-CloudLinux-Imunifyz1/etc/apt/trusted.gpg.d/RPM-GPG-KEY-CloudLinux.gpgreturnctD]F}|r0tjt |tjr|cSGdSN) _PUBKEY_PATHSis_fileosaccessstrR_OKps Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/doctor.py _find_pubkeyrsK  99;; 29SVVRW55 HHH 4urldstcFtj|}tj|t5}|d5}t j||dddn #1swxYwYddddS#1swxYwYdS)N)timeoutwb)urllibrequestRequesturlopen _HTTP_TIMEOUTopenshutil copyfileobj)rrreqrespfps r_blocking_downloadr)&s .  % %C   ]  ; ;%tSXX FF% 4$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%s6BA>2 B>B BB BBBc`t}|tjdsdS tdddt t jdtt}n9#t$r,}t dt|Yd}~dSd}~wwxYw | }tj|js5tj|jr|jt%jkr%tjt|d dS|d z d nV#t$rI}t d |tjt|d Yd}~dSd}~wwxYw||fS) zLocate the pubkey + gpg binary and create a validated 0700 workdir. Returns (pubkey_path, workdir_path) on success or None on failure; any partial state is removed before returning. NgpgT)modeparentsexist_okzimunify-doctor.)prefixdirz#cannot prepare workdir under %s: %s ignore_errorsgnupg)r-zworkdir setup failed: %s)rr$which_TMPDIRmkdirrtempfilemkdtemprOSErrorloggerinfolstatstatS_ISLNKst_modeS_ISDIRst_uidrgeteuidrmtree)pubkeyworkdirexcsts r_blocking_setup_workdirrI.s ^^F ~V\%00~t 5$ >>>  $53w<< H H H    97CHHHttttt ]]__ L $ $ < ++ yBJLL(( M#g,,d ; ; ; ;4 7 !!u!----  .444 c'll$7777ttttt 7?s7AA;; B1!B,,B15BE<E F) >F$$F)rcLtjt|ddS)NTr2)r$rDrrs r_blocking_rmtreerKTs# M#a&&------rr-c0||dSr )chmod)rr-s r_blocking_chmodrNXsGGDMMMMMrcvKtj}|dt||d{VdS)zFetch *url* to *dst* without blocking the event loop. Raises urllib.error.URLError (subclass of OSError) on any HTTP/transport error, which the caller's `except OSError` already handles. N)asyncioget_event_looprun_in_executorr))rrloops r _downloadrT\sI  ! # #D   t%7c B BBBBBBBBBBrc Ktj}|dtd{V}|dS|\}}|tz }|tdzz }|dz }d} t t |d{Vt t|d{Vttj t|}tdddd t|g| d{Vtdddd t|t|g| d{V|dt|d d{Vd }||s#|dt|d{VSS#tt f$rL} t"d| Yd} ~ |s$|dt|d{VdSdSd} ~ wwxYw#|s#|dt|d{VwwxYw)a# Download imunify-doctor.sh + .sig into /var/imunify360/tmp and verify the detached signature against an ephemeral keyring seeded with the CloudLinux pubkey. Returns the verified script on success or None on any failure (so the caller can fall back to the package copy). Nr r4F) GNUPGHOMEr+z--batchz--quietz--import)envz--verifyr,Tz%signed remote doctor fetch failed: %s)rPrQrRrI _SCRIPT_NAMErT _SCRIPT_URL_SIG_URLdictrenvironrrrNrKrr:r;r<) rSsetuprErFscriptsiggpghomesuccessrWrGs r_verified_remote_scriptrbfs  ! # #D&&t-DEE E E E E E EE }tOFG | #F \F* +CGGH V,,,,,,,,,#&&&&&&&&&2:W666 Iy*c&kk B           Iy*c#hhF L          ""4&%HHHHHHHHH  H&&t-=wGG G G G G G G G G H 7 # ;SAAAttt H&&t-=wGG G G G G G G G G G H H  H&&t-=wGG G G G G G G G G Hs+C$E))G:GG GG 'G0cKtd{V}|tdtj} t t |gd{V}|dt|jd{Vn,#|dt|jd{VwxYw| }|std|S)Nz)Signed remote doctor script not availablezDoctor key is empty) rb ValueErrorrPrQrrrRrKparentdecodestrip)r^rSoutkeys r_repo_get_doctor_keyrjs *,, , , , , , ,F ~DEEE  ! # #DJs6{{m,,,,,,,,""4)96=IIIIIIIIIId""4)96=IIIIIIIIII **,,    C 0./// Js #B)B0cKtj}t|sd}t t|dt gd{V}|}|S)Nz%/opt/imunify360/venv/share/imunify360scripts)rDATADIRris_dirrrXrfrg)dir_rhris r_package_get_doctor_keyrpsy  D ::    764i>>?@@ @ @ @ @ @ @C **,,    C JrcK td{V}n1#tttf$rt d{V}YnwxYwt dd|i|S)N doctor_key)rjrrdr:rpr)ris rget_doctor_keyrss.(******** :w /...+--------.|lC0111 Js+AA)(rPloggingrr$r>r8urllib.requestrpathlibrtypingr defence360agent.contracts.configr'defence360agent.subsys.persistent_staterdefence360agent.utilsrrr" getLogger__name__r;rXrYrZr6rrrr)rIrKintrNrTrbrjrprsrrrsV 666666>>>>>>::::::::  8 $ $" 8<G    $$ % %D :;;D <== htn%C%d%t%%%%###L......t34CC4CDCCCC&Hx~&H&H&H&HR C    srdefence360agent/utils/__pycache__/doctor.cpython-311.pyc0000644000000000000000000002424300000000000020117 0ustar r_jddlZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZmZdZejeZdZd ezZed zZe d Ze d e d fZde e fdZdede ddfdZdZde ddfdZ de de!ddfdZ"dede ddfdZ#de e fdZ$defdZ%defdZ&dZ'dS)N)Path)Optional) Packaging) save_state) CheckRunError check_runzimunify-doctor.shz2https://repo.imunify360.cloudlinux.com/defence360/.sigz/var/imunify360/tmpz//etc/pki/rpm-gpg/RPM-GPG-KEY-CloudLinux-Imunifyz1/etc/apt/trusted.gpg.d/RPM-GPG-KEY-CloudLinux.gpgreturnctD]F}|r0tjt |tjr|cSGdSN) _PUBKEY_PATHSis_fileosaccessstrR_OKps Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/doctor.py _find_pubkeyrsK  99;; 29SVVRW55 HHH 4urldstcFtj|}tj|t5}|d5}t j||dddn #1swxYwYddddS#1swxYwYdS)N)timeoutwb)urllibrequestRequesturlopen _HTTP_TIMEOUTopenshutil copyfileobj)rrreqrespfps r_blocking_downloadr)&s .  % %C   ]  ; ;%tSXX FF% 4$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%s6BA>2 B>B BB BBBc`t}|tjdsdS tdddt t jdtt}n9#t$r,}t dt|Yd}~dSd}~wwxYw | }tj|js5tj|jr|jt%jkr%tjt|d dS|d z d nV#t$rI}t d |tjt|d Yd}~dSd}~wwxYw||fS) zLocate the pubkey + gpg binary and create a validated 0700 workdir. Returns (pubkey_path, workdir_path) on success or None on failure; any partial state is removed before returning. NgpgT)modeparentsexist_okzimunify-doctor.)prefixdirz#cannot prepare workdir under %s: %s ignore_errorsgnupg)r-zworkdir setup failed: %s)rr$which_TMPDIRmkdirrtempfilemkdtemprOSErrorloggerinfolstatstatS_ISLNKst_modeS_ISDIRst_uidrgeteuidrmtree)pubkeyworkdirexcsts r_blocking_setup_workdirrI.s ^^F ~V\%00~t 5$ >>>  $53w<< H H H    97CHHHttttt ]]__ L $ $ < ++ yBJLL(( M#g,,d ; ; ; ;4 7 !!u!----  .444 c'll$7777ttttt 7?s7AA;; B1!B,,B15BE<E F) >F$$F)rcLtjt|ddS)NTr2)r$rDrrs r_blocking_rmtreerKTs# M#a&&------rr-c0||dSr )chmod)rr-s r_blocking_chmodrNXsGGDMMMMMrcvKtj}|dt||d{VdS)zFetch *url* to *dst* without blocking the event loop. Raises urllib.error.URLError (subclass of OSError) on any HTTP/transport error, which the caller's `except OSError` already handles. N)asyncioget_event_looprun_in_executorr))rrloops r _downloadrT\sI  ! # #D   t%7c B BBBBBBBBBBrc Ktj}|dtd{V}|dS|\}}|tz }|tdzz }|dz }d} t t |d{Vt t|d{Vttj t|}tdddd t|g| d{Vtdddd t|t|g| d{V|dt|d d{Vd }||s#|dt|d{VSS#tt f$rL} t"d| Yd} ~ |s$|dt|d{VdSdSd} ~ wwxYw#|s#|dt|d{VwwxYw)a# Download imunify-doctor.sh + .sig into /var/imunify360/tmp and verify the detached signature against an ephemeral keyring seeded with the CloudLinux pubkey. Returns the verified script on success or None on any failure (so the caller can fall back to the package copy). Nr r4F) GNUPGHOMEr+z--batchz--quietz--import)envz--verifyr,Tz%signed remote doctor fetch failed: %s)rPrQrRrI _SCRIPT_NAMErT _SCRIPT_URL_SIG_URLdictrenvironrrrNrKrr:r;r<) rSsetuprErFscriptsiggpghomesuccessrWrGs r_verified_remote_scriptrbfs  ! # #D&&t-DEE E E E E E EE }tOFG | #F \F* +CGGH V,,,,,,,,,#&&&&&&&&&2:W666 Iy*c&kk B           Iy*c#hhF L          ""4&%HHHHHHHHH  H&&t-=wGG G G G G G G G G H 7 # ;SAAAttt H&&t-=wGG G G G G G G G G G H H  H&&t-=wGG G G G G G G G G Hs+C$E))G:GG GG 'G0cKtd{V}|tdtj} t t |gd{V}|dt|jd{Vn,#|dt|jd{VwxYw| }|std|S)Nz)Signed remote doctor script not availablezDoctor key is empty) rb ValueErrorrPrQrrrRrKparentdecodestrip)r^rSoutkeys r_repo_get_doctor_keyrjs *,, , , , , , ,F ~DEEE  ! # #DJs6{{m,,,,,,,,""4)96=IIIIIIIIIId""4)96=IIIIIIIIII **,,    C 0./// Js #B)B0cKtj}t|sd}t t|dt gd{V}|}|S)Nz%/opt/imunify360/venv/share/imunify360scripts)rDATADIRris_dirrrXrfrg)dir_rhris r_package_get_doctor_keyrpsy  D ::    764i>>?@@ @ @ @ @ @ @C **,,    C JrcK td{V}n1#tttf$rt d{V}YnwxYwt dd|i|S)N doctor_key)rjrrdr:rpr)ris rget_doctor_keyrss.(******** :w /...+--------.|lC0111 Js+AA)(rPloggingrr$r>r8urllib.requestrpathlibrtypingr defence360agent.contracts.configr'defence360agent.subsys.persistent_staterdefence360agent.utilsrrr" getLogger__name__r;rXrYrZr6rrrr)rIrKintrNrTrbrjrprsrrrsV 666666>>>>>>::::::::  8 $ $" 8<G    $$ % %D :;;D <== htn%C%d%t%%%%###L......t34CC4CDCCCC&Hx~&H&H&H&HR C    srdefence360agent/utils/__pycache__/fd_ops.cpython-311.opt-1.pyc0000644000000000000000000002430600000000000021036 0ustar r_jdZddlZddlZddlZddlZddlmZmZddlm Z ej e Z ddZ defdZeejfddd Zed Zd ed edefd ZdS)a[fd-based file operations for symlink-attack mitigation. All helpers in this module use O_NOFOLLOW and dir_fd-relative syscalls so that no path-based resolution can be redirected by a concurrent symlink swap. This module is intentionally kept separate from utils/__init__.py to avoid loading these OS-specific helpers into every agent component. N)contextmanagersuppress)Pathreturnc|dfg} |r$|d\}}d}tj|5}|D]}|dr`tj|jtjtjztjz|}|||jfd}ntj |j|dddn #1swxYwY|sN| \}} | 5tj ||d\} }tj | | |"dSdS#t$r |D]\}} | tj |wxYw)uRemove all contents of a directory using fd-relative operations. Every entry is opened with ``O_NOFOLLOW`` so symlinks inside the tree are unlinked rather than followed. The directory referenced by *dir_fd* itself is **not** removed — the caller should ``os.rmdir()`` the parent entry after this call returns. Uses an iterative approach with an explicit stack to avoid hitting Python's recursion limit on adversarial deeply-nested trees. *dir_fd* must be an open ``O_RDONLY | O_DIRECTORY`` descriptor. NF)follow_symlinksdir_fdT)osscandiris_diropennameO_RDONLY O_DIRECTORY O_NOFOLLOWappendunlinkpopclosermdir BaseException) r stack current_fd_pushedentriesentrychild_fdfdr parent_fds Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/fd_ops.py rmtree_fdr$s"d^ E 5!"IMJFJ'' A7$ A AE||E|:: A#%7!JK".82=H#-$$$  h %;<<#t$r1tjd}tj|d |z}YnwxYwd}d }t+d D]}|d tjd d} tj|tjtjztjztjzd|}n#t6$rYwxYwt7d t9|}d}|t|kr3|tj|||dz }|t|k3||tj|||tj||tj |tj!|d }tj"||||d}|dkrtj!||BtGt5tj$||dddn #1swxYwYnd#|dkrtj!||CtGt5tj$||dddw#1swxYwYwwxYwdS)adir_fd-relative implementation of atomic_rewrite. The caller opens the directory with O_NOFOLLOW before any file I/O begins. All file operations use dir_fd so that a concurrent rename of the directory to a symlink cannot redirect writes to a privileged path. r rbr&NFzempty content: %r for file: %s)r r rirdrz .i360editiz.Could not create temporary file (100 attempts)) src_dir_fd dst_dir_fdT)%r r'splitrrrfdopenreadlenFileNotFoundErrorOSErrorerrnoELOOPloggererrorstatS_ISLNKst_modestrerrorS_IMODEumaskrangeurandomhexO_WRONLYO_CREATO_EXCLFileExistsError memoryviewwritechownchmodfsyncrrenamerr)filenamer5uidgidallow_empty_content permissionsr rbasename content_fdf old_contentexcst current_umask tmp_basenametmp_fdviewwrittens r#atomic_rewrite_fdris"'--))KAxW bkBM1&   Yz4 ( ( 0A&&TQ//K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $  5        9 # #   DDDD t 5tXFFFu 1&%HHHB|BJ'' Oek2;u{+C+CXNNN,rz22KK  1 1 1HQKKM H] # # #=.0KKK 1L F 3ZZ P P"CCRZ]]%6%6%8%8CCC  W bj(294r}D F E    H NOOO7$D !! rxWXX77 7GD !! ?s HVS# & & & %%%   ,VOOOO Q;; HV     #+,, 7 7 ,v6666 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 Q;; HV     #+,, 7 7 ,v6666 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 $ 4sAB-'&B B-BB- B! B-- C#9 C#CC# BF 8GG A I I$#I$7C N 1NNN 1PO4( P4O8 8P;O8 <P)rN)__doc__rBloggingr rF contextlibrrpathlibr getLogger__name__rDr$intr-rr2r4bytesboolrir#rus0  ////////  8 $ $0000f#: kT"   [ [ [ [[[[[[rtdefence360agent/utils/__pycache__/fd_ops.cpython-311.pyc0000644000000000000000000002430600000000000020077 0ustar r_jdZddlZddlZddlZddlZddlmZmZddlm Z ej e Z ddZ defdZeejfddd Zed Zd ed edefd ZdS)a[fd-based file operations for symlink-attack mitigation. All helpers in this module use O_NOFOLLOW and dir_fd-relative syscalls so that no path-based resolution can be redirected by a concurrent symlink swap. This module is intentionally kept separate from utils/__init__.py to avoid loading these OS-specific helpers into every agent component. N)contextmanagersuppress)Pathreturnc|dfg} |r$|d\}}d}tj|5}|D]}|dr`tj|jtjtjztjz|}|||jfd}ntj |j|dddn #1swxYwY|sN| \}} | 5tj ||d\} }tj | | |"dSdS#t$r |D]\}} | tj |wxYw)uRemove all contents of a directory using fd-relative operations. Every entry is opened with ``O_NOFOLLOW`` so symlinks inside the tree are unlinked rather than followed. The directory referenced by *dir_fd* itself is **not** removed — the caller should ``os.rmdir()`` the parent entry after this call returns. Uses an iterative approach with an explicit stack to avoid hitting Python's recursion limit on adversarial deeply-nested trees. *dir_fd* must be an open ``O_RDONLY | O_DIRECTORY`` descriptor. NF)follow_symlinksdir_fdT)osscandiris_diropennameO_RDONLY O_DIRECTORY O_NOFOLLOWappendunlinkpopclosermdir BaseException) r stack current_fd_pushedentriesentrychild_fdfdr parent_fds Q/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/fd_ops.py rmtree_fdr$s"d^ E 5!"IMJFJ'' A7$ A AE||E|:: A#%7!JK".82=H#-$$$  h %;<<#t$r1tjd}tj|d |z}YnwxYwd}d }t+d D]}|d tjd d} tj|tjtjztjztjzd|}n#t6$rYwxYwt7d t9|}d}|t|kr3|tj|||dz }|t|k3||tj|||tj||tj |tj!|d }tj"||||d}|dkrtj!||BtGt5tj$||dddn #1swxYwYnd#|dkrtj!||CtGt5tj$||dddw#1swxYwYwwxYwdS)adir_fd-relative implementation of atomic_rewrite. The caller opens the directory with O_NOFOLLOW before any file I/O begins. All file operations use dir_fd so that a concurrent rename of the directory to a symlink cannot redirect writes to a privileged path. r rbr&NFzempty content: %r for file: %s)r r rirdrz .i360editiz.Could not create temporary file (100 attempts)) src_dir_fd dst_dir_fdT)%r r'splitrrrfdopenreadlenFileNotFoundErrorOSErrorerrnoELOOPloggererrorstatS_ISLNKst_modestrerrorS_IMODEumaskrangeurandomhexO_WRONLYO_CREATO_EXCLFileExistsError memoryviewwritechownchmodfsyncrrenamerr)filenamer5uidgidallow_empty_content permissionsr rbasename content_fdf old_contentexcst current_umask tmp_basenametmp_fdviewwrittens r#atomic_rewrite_fdris"'--))KAxW bkBM1&   Yz4 ( ( 0A&&TQ//K 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 $  5        9 # #   DDDD t 5tXFFFu 1&%HHHB|BJ'' Oek2;u{+C+CXNNN,rz22KK  1 1 1HQKKM H] # # #=.0KKK 1L F 3ZZ P P"CCRZ]]%6%6%8%8CCC  W bj(294r}D F E    H NOOO7$D !! rxWXX77 7GD !! ?s HVS# & & & %%%   ,VOOOO Q;; HV     #+,, 7 7 ,v6666 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 Q;; HV     #+,, 7 7 ,v6666 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 $ 4sAB-'&B B-BB- B! B-- C#9 C#CC# BF 8GG A I I$#I$7C N 1NNN 1PO4( P4O8 8P;O8 <P)rN)__doc__rBloggingr rF contextlibrrpathlibr getLogger__name__rDr$intr-rr2r4bytesboolrir#rus0  ////////  8 $ $0000f#: kT"   [ [ [ [[[[[[rtdefence360agent/utils/__pycache__/hyperscan.cpython-311.opt-1.pyc0000644000000000000000000000134700000000000021560 0ustar r_jBddlZejddZdS)N)maxsizectd5}d|vcdddS#1swxYwYdS)Nz /proc/cpuinfossse3)openread)fs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/hyperscan.pyis_ssse3_supportedr s o  #!!&&(("##################s 377) functools lru_cacher r rsGQ## ###rdefence360agent/utils/__pycache__/hyperscan.cpython-311.pyc0000644000000000000000000000134700000000000020621 0ustar r_jBddlZejddZdS)N)maxsizectd5}d|vcdddS#1swxYwYdS)Nz /proc/cpuinfossse3)openread)fs T/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/hyperscan.pyis_ssse3_supportedr s o  #!!&&(("##################s 377) functools lru_cacher r rsGQ## ###rdefence360agent/utils/__pycache__/importer.cpython-311.opt-1.pyc0000644000000000000000000001165000000000000021423 0ustar r_j dZddlZddlZddlZddlZddlmZddlmZm Z m Z ej e Z dede eefddfd Zd e e eefded fd ZddeddfdZddeddfdZdZdZGddZdS)z> Provides utilities for dynamically loading packages/modules. N)Path) GeneratorListUnion module_name file_pathreturnmodulectj||}tj|}|j||S)z4 Execute and return module from *file_path* ) importlibutilspec_from_file_locationmodule_from_specloader exec_module)rrspecr s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/importer.pyget_module_by_pathrsK > 1 1+y I ID ^ , ,T 2 2FKF### Mpaths)r NNc#Ktj|D]D}|js;t|jj|jdz }t|j|VEdS)z) Yields all modules from *paths* z.pyN)pkgutil iter_modulesispkgr module_finderpathnamer)rr rs rrrsr &u--88| 8,122 5H5H5HHD$V[$77 7 7 788rFrcR tj|}n#t$r|sYdSwxYwtj||j|jr<|}tj |j D]"}tj|d|j!dSdS)z Import *name* module, if *name* is a package import all submodules. If *name* module/package is not found: - raise ModuleNotFoundError if *missing_ok* is False - ignore it if *missing_ok* is True N.) r r find_specModuleNotFoundError import_moduler is_packagerrrsubmodule_search_locations)r missing_okrpackager s rloadr''s~''--    D!!! {di((@*4+JKK @ @F  #w$>$>$>$> ? ? ? ?@@ @ @s " 33packagesc2|D]}t||dS)N)r%)r')r(r%r&s r load_packagesr*<s1-- W,,,,,--rct tj|}n#t$r|cYSwxYwt|||S)zh Return object with *name* from specific *module*. If object was not found return *default* )r r" ImportErrorgetattr)r rdefaultms rgetr0AsO   #F + +  1dG $ $$s  &&cn tj|}n#t$rYdSwxYw|duS)NF)r r r r!)rrs rexistsr2MsK~''-- uu t s " 00c6eZdZdefdZedZdZdS) LazyImportrc"||_d|_dSN) _module_name_module)selfrs r__init__zLazyImport.__init__Vs' rcZ|jtj|j|_|jSr6)r8r r"r7)r9s rr zLazyImport.moduleZs' < $243DEEDL|rc,t|j|Sr6)r-r )r9attrs r __getattr__zLazyImport.__getattr__`st{D)))rN)__name__ __module__ __qualname__strr:propertyr r>rrr4r4UsYCX *****rr4)F)__doc__r importlib.utilloggingrpathlibrtypingrrr getLoggerr?loggerrBrrr'tupler*r0r2r4rDrrrMs))))))))))  8 $ $  !&sDy!1       8 c4i ! 8#$ 8 8 8 8@@s@@@@@*--E----- % % % * * * * * * * * * *rdefence360agent/utils/__pycache__/importer.cpython-311.pyc0000644000000000000000000001165000000000000020464 0ustar r_j dZddlZddlZddlZddlZddlmZddlmZm Z m Z ej e Z dede eefddfd Zd e e eefded fd ZddeddfdZddeddfdZdZdZGddZdS)z> Provides utilities for dynamically loading packages/modules. N)Path) GeneratorListUnion module_name file_pathreturnmodulectj||}tj|}|j||S)z4 Execute and return module from *file_path* ) importlibutilspec_from_file_locationmodule_from_specloader exec_module)rrspecr s S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/importer.pyget_module_by_pathrsK > 1 1+y I ID ^ , ,T 2 2FKF### Mpaths)r NNc#Ktj|D]D}|js;t|jj|jdz }t|j|VEdS)z) Yields all modules from *paths* z.pyN)pkgutil iter_modulesispkgr module_finderpathnamer)rr rs rrrsr &u--88| 8,122 5H5H5HHD$V[$77 7 7 788rFrcR tj|}n#t$r|sYdSwxYwtj||j|jr<|}tj |j D]"}tj|d|j!dSdS)z Import *name* module, if *name* is a package import all submodules. If *name* module/package is not found: - raise ModuleNotFoundError if *missing_ok* is False - ignore it if *missing_ok* is True N.) r r find_specModuleNotFoundError import_moduler is_packagerrrsubmodule_search_locations)r missing_okrpackager s rloadr''s~''--    D!!! {di((@*4+JKK @ @F  #w$>$>$>$> ? ? ? ?@@ @ @s " 33packagesc2|D]}t||dS)N)r%)r')r(r%r&s r load_packagesr*<s1-- W,,,,,--rct tj|}n#t$r|cYSwxYwt|||S)zh Return object with *name* from specific *module*. If object was not found return *default* )r r" ImportErrorgetattr)r rdefaultms rgetr0AsO   #F + +  1dG $ $$s  &&cn tj|}n#t$rYdSwxYw|duS)NF)r r r r!)rrs rexistsr2MsK~''-- uu t s " 00c6eZdZdefdZedZdZdS) LazyImportrc"||_d|_dSN) _module_name_module)selfrs r__init__zLazyImport.__init__Vs' rcZ|jtj|j|_|jSr6)r8r r"r7)r9s rr zLazyImport.moduleZs' < $243DEEDL|rc,t|j|Sr6)r-r )r9attrs r __getattr__zLazyImport.__getattr__`st{D)))rN)__name__ __module__ __qualname__strr:propertyr r>rrr4r4UsYCX *****rr4)F)__doc__r importlib.utilloggingrpathlibrtypingrrr getLoggerr?loggerrBrrr'tupler*r0r2r4rDrrrMs))))))))))  8 $ $  !&sDy!1       8 c4i ! 8#$ 8 8 8 8@@s@@@@@*--E----- % % % * * * * * * * * * *rdefence360agent/utils/__pycache__/ipecho.cpython-311.opt-1.pyc0000644000000000000000000001312500000000000021030 0ustar r_j dZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZmZejeZd Zd Zed d z ZGd de ZdS)z>Cr+c tt|dddS#t$r&}td|Yd}~dSd}~wwxYw)NFi)backup permissionszIPEchoAPI cache write error: %s)rr-rr5r6)rr+rs r _save_cachezIPEchoAPI._save_cacheTs| ? !         ? ? ? LL:A > > > > > > > > > ?s A AA c`|}||Stj|j|jz}||}|ddkrtd|d}|r|||S)zIGet IP from file-based cache or send request to API and process response.NstatusokzUnexpected API errorr+) r9urllibrequestRequest _BASE_URLURLgetrr=)r cached_iprBresponser+s rrzIPEchoAPI._get_ip`sOO%%   .(()@AA;;w'' << ! !T ) )122 2 \\$    OOB    r)N)__name__ __module__ __qualname____doc__rE classmethodrr rstrr functools lru_cacherrr9r=rrrrrsR<< CZ44i48C=444[4 Y###""$#["%)"""" #"""[""HSM[( ?S ?T ? ? ?[ ?[rr)rLr"rOloggingr1rApathlibrtypingr async_lrurdefence360agent.api.serverrrdefence360agent.utilsrdefence360agent.utils.validater r getLoggerrIr5r&r2r-rrQrrrZs$BB  4444444400000088888888  8 $ $$())N:XXXXXXXXXXrdefence360agent/utils/__pycache__/ipecho.cpython-311.pyc0000644000000000000000000001312500000000000020071 0ustar r_j dZddlZddlZddlZddlZddlZddlmZddlm Z ddl m Z ddl m Z mZddlmZddlmZmZejeZd Zd Zed d z ZGd de ZdS)z>Cr+c tt|dddS#t$r&}td|Yd}~dSd}~wwxYw)NFi)backup permissionszIPEchoAPI cache write error: %s)rr-rr5r6)rr+rs r _save_cachezIPEchoAPI._save_cacheTs| ? !         ? ? ? LL:A > > > > > > > > > ?s A AA c`|}||Stj|j|jz}||}|ddkrtd|d}|r|||S)zIGet IP from file-based cache or send request to API and process response.NstatusokzUnexpected API errorr+) r9urllibrequestRequest _BASE_URLURLgetrr=)r cached_iprBresponser+s rrzIPEchoAPI._get_ip`sOO%%   .(()@AA;;w'' << ! !T ) )122 2 \\$    OOB    r)N)__name__ __module__ __qualname____doc__rE classmethodrr rstrr functools lru_cacherrr9r=rrrrrsR<< CZ44i48C=444[4 Y###""$#["%)"""" #"""[""HSM[( ?S ?T ? ? ?[ ?[rr)rLr"rOloggingr1rApathlibrtypingr async_lrurdefence360agent.api.serverrrdefence360agent.utilsrdefence360agent.utils.validater r getLoggerrIr5r&r2r-rrQrrrZs$BB  4444444400000088888888  8 $ $$())N:XXXXXXXXXXrdefence360agent/utils/__pycache__/json.cpython-311.opt-1.pyc0000644000000000000000000000460100000000000020531 0ustar r_jdZddlZddlmZmZmZmZddlmZddl m Z de fdZ Gdd ej ZGd d eZdS) z6JSON encoders to help with sending messages to server.N) IPv4Address IPv4Network IPv6Address IPv6Network) model_to_dict)Modelreturncpt|jst|jSt|S)zn IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' )inthostmaskstrnetwork_address)nets O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/json.pyip_net_to_stringr s2 s|  (3&''' s88OceZdZdZdS) IPEncoderct|ttfrt|St|tt frt |Stj ||SN) isinstancerrrrrr json JSONEncoderdefault)selfobjs rrzIPEncoder.defaultsb cK5 6 6 )#C(( ( cK5 6 6 s88O''c222rN)__name__ __module__ __qualname__rrrrrs#33333rrceZdZfdZxZS)ServerJSONEncoderct|trt|St|Sr)rrrsuperr)rr __class__s rrzServerJSONEncoder.defaults9 c5 ! ! & %% %wws###r)rrrr __classcell__)r%s@rr"r"s8$$$$$$$$$rr")__doc__r ipaddressrrrrplayhouse.shortcutsrdefence360agent.modelrr rrrr"r rrr+s<< HHHHHHHHHHHH------''''''S33333 333$$$$$ $$$$$rdefence360agent/utils/__pycache__/json.cpython-311.pyc0000644000000000000000000000460100000000000017572 0ustar r_jdZddlZddlmZmZmZmZddlmZddl m Z de fdZ Gdd ej ZGd d eZdS) z6JSON encoders to help with sending messages to server.N) IPv4Address IPv4Network IPv6Address IPv6Network) model_to_dict)Modelreturncpt|jst|jSt|S)zn IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' )inthostmaskstrnetwork_address)nets O/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/json.pyip_net_to_stringr s2 s|  (3&''' s88OceZdZdZdS) IPEncoderct|ttfrt|St|tt frt |Stj ||SN) isinstancerrrrrr json JSONEncoderdefault)selfobjs rrzIPEncoder.defaultsb cK5 6 6 )#C(( ( cK5 6 6 s88O''c222rN)__name__ __module__ __qualname__rrrrrs#33333rrceZdZfdZxZS)ServerJSONEncoderct|trt|St|Sr)rrrsuperr)rr __class__s rrzServerJSONEncoder.defaults9 c5 ! ! & %% %wws###r)rrrr __classcell__)r%s@rr"r"s8$$$$$$$$$rr")__doc__r ipaddressrrrrplayhouse.shortcutsrdefence360agent.modelrr rrrr"r rrr+s<< HHHHHHHHHHHH------''''''S33333 333$$$$$ $$$$$rdefence360agent/utils/__pycache__/kwconfig.cpython-311.opt-1.pyc0000644000000000000000000000667500000000000021404 0ustar r_j\ddlZddlmZddlmZGddZGddeZdS)N)Optional)atomic_rewritecreZdZdZdxZxZZdZd dZde e fdZ de e fdZ de e fd Z dS) KWConfigz Basic class for working with key-value configuration files Subclasses must define SEARCH_PATTERN and WRITE_PATTERN attributes TNc|jsJtj|j|tj|_|p|j|_||_dSN) SEARCH_PATTERNrecompileformat MULTILINE_patternDEFAULT_FILENAME _filename_name)selfnamefilenames S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/kwconfig.py__init__zKWConfig.__init__sX""""   & &t , ,bl   ":T%: returnc|jsJt|j5}|}dddn #1swxYwY||}|*|d|j|j|zdzz }n9|j|j|j||}t|j||j |S)N )allow_empty_content) WRITE_PATTERNopenrread_parser rrsubrALLOW_EMPTY_CONFIG)rvaluefcontent old_values rsetz KWConfig.sets)!!!! $. ! ! QffhhG               KK((   t)00UCCCdJ GGm''"))$*e<r9s 0000002(2(2(2(2(2(2(2(j----------rdefence360agent/utils/__pycache__/kwconfig.cpython-311.pyc0000644000000000000000000000667500000000000020445 0ustar r_j\ddlZddlmZddlmZGddZGddeZdS)N)Optional)atomic_rewritecreZdZdZdxZxZZdZd dZde e fdZ de e fdZ de e fd Z dS) KWConfigz Basic class for working with key-value configuration files Subclasses must define SEARCH_PATTERN and WRITE_PATTERN attributes TNc|jsJtj|j|tj|_|p|j|_||_dSN) SEARCH_PATTERNrecompileformat MULTILINE_patternDEFAULT_FILENAME _filename_name)selfnamefilenames S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/kwconfig.py__init__zKWConfig.__init__sX""""   & &t , ,bl   ":T%: returnc|jsJt|j5}|}dddn #1swxYwY||}|*|d|j|j|zdzz }n9|j|j|j||}t|j||j |S)N )allow_empty_content) WRITE_PATTERNopenrread_parser rrsubrALLOW_EMPTY_CONFIG)rvaluefcontent old_values rsetz KWConfig.sets)!!!! $. ! ! QffhhG               KK((   t)00UCCCdJ GGm''"))$*e<r9s 0000002(2(2(2(2(2(2(2(j----------rdefence360agent/utils/__pycache__/net.cpython-311.opt-1.pyc0000644000000000000000000000245000000000000020346 0ustar r_jSddlmZmZmZmZddlmZmZdZd\Z Z deeeffdZ deeefdee e e ffd Z d S) ) IPv4Address IPv4Network IPv6Address IPv6Network)TupleUniontcp)inout ip_addressc|jdkr*t|jddddSt|S)NbigT)signed)versionint from_bytespacked)r s N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net.pypack_ip_addressrs@Q~~j/3U4~HHH: ip_networkreturncft|j}t|j}|||jfS)N)rnetwork_addressnetmaskr)rnetmasks rpack_ip_networkr s4 *4 5 5C :- . .D j( ((rN) ipaddressrrrrtypingrrTCPINOUTrrr rrr'sHHHHHHHHHHHH  Ck;&> ?)k;./) 3S=))))))rdefence360agent/utils/__pycache__/net.cpython-311.pyc0000644000000000000000000000245000000000000017407 0ustar r_jSddlmZmZmZmZddlmZmZdZd\Z Z deeeffdZ deeefdee e e ffd Z d S) ) IPv4Address IPv4Network IPv6Address IPv6Network)TupleUniontcp)inout ip_addressc|jdkr*t|jddddSt|S)NbigT)signed)versionint from_bytespacked)r s N/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net.pypack_ip_addressrs@Q~~j/3U4~HHH: ip_networkreturncft|j}t|j}|||jfS)N)rnetwork_addressnetmaskr)rnetmasks rpack_ip_networkr s4 *4 5 5C :- . .D j( ((rN) ipaddressrrrrtypingrrTCPINOUTrrr rrr'sHHHHHHHHHHHH  Ck;&> ?)k;./) 3S=))))))rdefence360agent/utils/__pycache__/net_transport.cpython-311.opt-1.pyc0000644000000000000000000004700000000000000022462 0ustar r_jp0dZddlZddlZddlZddlZddlZddlZddlZ ddl m Z m Z ddl mZddlmZmZmZmZerddlZeeZdZdedefd ZGd d e ZGd d ZGddeZGddeZGddejj Z!Gddejj"Z#Gdde j$j%Z&Gdde j$j'Z(GddZ)dS)avNetworking transport helpers for urllib. This module provides a small abstraction on top of urllib.request so that callers can keep using urllib.request.Request, but routing of connections can be customized: - hostname resolution is handled in user code; - selected IP may be randomized or chosen using any complex logic; - for HTTPS: connects to a chosen IP but keeps correct SNI and certificate hostname validation for the original hostname (NOT the IP). Examples: Default behavior (plain urllib): from defence360agent.utils.net_transport import UrlTransport transport = UrlTransport() req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Randomize target IP on each connection (A/AAAA -> random choice): from defence360agent.utils.net_transport import UrlTransport, RandomIpChooser chooser = RandomIpChooser() transport = UrlTransport(ip_chooser=chooser) req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Notes: - HTTPS: connects to the chosen IP but keeps SNI/cert checks against original hostname. - HTTP: Host header stays original hostname because urllib builds it from the URL. N)ABCabstractmethod) getLogger)DictOptionalTuple TYPE_CHECKINGgr@ipreturnc~ ttj|tjS#t$rYdSwxYw)z~Return True if *ip* is an IPv4 address string. Implementation relies solely on ipaddress.ip_address for correctness. F) isinstance ipaddress ip_address IPv4Address ValueError)r s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net_transport.py_is_ipv4rCsG ).r22I4IJJJ uus +. <<cJeZdZdZedededefdZdededefdZdS) IpChooserzSelect an IP address to connect to for a given hostname and port. Implementations may be stateful and can keep caches/metrics inside. hostnameportr ct)z6Return an IP address (v4 or v6) for *hostname*:*port*.)NotImplementedErrorselfrrs rchoosezIpChooser.chooseTs "!c.|||SN)rrs r__call__zIpChooser.__call__Ys{{8T***rN) __name__ __module__ __qualname____doc__rstrintrr rrrrNs| "s"#"#"""^"++C+C++++++rrcXeZdZdZejeddedefdZ de dede e d ffd Z d S) DnsCacheResolverzDNS cache for socket.getaddrinfo() results. It caches per (hostname, port, family). This is intentionally small and local: it is meant only to avoid excessive getaddrinfo() calls. )family ttl_secondsr*r+c`||_||_i|_tj|_dSr)_family _ttl_seconds_cache threadingLock_lock)rr*r+s r__init__zDnsCacheResolver.__init__ds4  '  ^%% rrrr .c,|||jf}tj}|j5|j|}|;|\}}||kr0t d|||j|cdddSdddn #1swxYwYt d|||jtj|||jtj }g}|D])\} } } } } | d} | |vr| | *|s#td ||t|} |j5||jz| f|j|<dddn #1swxYwYt d|||j| | S)Nz0DnsCacheResolver cache hit for %s:%s (family=%s)z9DnsCacheResolver cache miss/expired for %s:%s (family=%s)rzNo IPs resolved for {}:{}z1DnsCacheResolver resolved %s:%s (family=%s) to %s)r-timer2r/getloggerdebugsocket getaddrinfo SOCK_STREAMappendOSErrorformattupler.) rrrkeynowcached expires_atipsinfos_sockaddrr ips_ts rget_ipszDnsCacheResolver.get_ipsqsnt|,ikk Z  [__S))F!"( C##LLJ                            G   L    "   L     $)   Aq!Q!B}} 2 N5<z7RandomIpChooserWithIPv6Toggle.choose..s-::Xb\\:::::::rzNo IPv4 IPs resolved for {}:{}zKRandomIpChooserWithIPv6Toggle selected IPv4 IP %s for %s:%s (IPv6 disabled)) rSrIrWrVchoicerXr7r8r?r=r>)rrrrDchosenipv4_ipss rrz$RandomIpChooserWithIPv6Toggle.choosesn$$Xt44   Y%%c**F"DM LL!    M::c::::: 077$GG !!(++           rr N)r!r"r#r$rr)rTrUrer3r\r^r`r%rbrfr&rr'rrrNrNs04'+! , , ,+, ,fm $ ,  , , , ,""""####"""""#>$>>>>s##rrNc`eZdZdZddddeedeejfdZde de d e fd Z dS) RandomIpChooserzAResolve hostname and select a random IP from resolved candidates.N)rOrPrOrPcd|p t|_|ptj|_dSr)r)rSrTrUrV)rrOrPs rr3zRandomIpChooser.__init__s- "7%5%7%7*6=?? rrrr c|j||}|j|}td||||S)Nz(RandomIpChooser selected IP %s for %s:%s)rSrIrVrkr7r8)rrrrDrls rrzRandomIpChooser.choosesWn$$Xt44!!#&& 6        r) r!r"r#r$rr)rTrUr3r%r&rr'rrrprpsKK 04'+ ++++,+fm $ ++++ s # #      rrpcZeZdZdZ d ejdddedeede ffdZ d d Z xZ S) ForcedIPHTTPConnectionzHTTPConnection that connects to a chosen IP. Important: urllib builds the request URL with the original hostname, therefore the Host header stays correct. Ntimeoutsource_addressrr ip_chooserc`t||||||_dS)N)rrvrwsuperr3 _ip_chooser)rrrrxrvrw __class__s rr3zForcedIPHTTPConnection.__init__ sB  )    &rr c|jpd}|j|j|}td|||jt j||f|j|j |_ dS)NPz:ForcedIPHTTPConnection connecting to %s:%s for hostname %s) rr|rhostr7r8r9create_connectionrvrwsock)rrr s rconnectzForcedIPHTTPConnection.connectswyB   $ $TY 5 5 H   I    , J L     rrrn r!r"r#r$r9_GLOBAL_DEFAULT_TIMEOUTr%rr&rr3r __classcell__r}s@rrtrts#& .&&&&sm&  &&&&&&"        rrtc ^eZdZdZ d ejdddedeede ddffd Z d d Z xZ S)ForcedIPHTTPSConnectionzHTTPSConnection that connects to a chosen IP. TLS details: - Uses original hostname for SNI (server_hostname in wrap_socket) - Certificate hostname validation is performed for the original hostname Nrurrrxcontextssl.SSLContextcbt|||||||_dS)N)rrrvrwrz)rrrrxrrvrwr}s rr3z ForcedIPHTTPSConnection.__init__3sE  )    &rr c~|jpd}|j|j|}td|||jt j||f|j|j }|j r"||_ | |j }|j ||j|_ dS)Niz;ForcedIPHTTPSConnection connecting to %s:%s for hostname %s)server_hostname)rr|rrr7r8r9rrvrw _tunnel_hostr_tunnel_context wrap_socket)rrr raw_socks rrzForcedIPHTTPSConnection.connectFsyC   $ $TY 5 5 I   I    + J L       ! DI LLNNNyHM--  I.   rrrnrrs@rrr+s#&.&&&&sm&  & " &&&&&&&        rrcHeZdZdZdeffd ZdejjfdZ xZ S)ForcedIPHTTPHandlerz3urllib handler that creates ForcedIPHTTPConnection.rxcVt||_dSrrz)rrxr}s rr3zForcedIPHTTPHandler.__init__ds' %rr c:fd}||S)NcXt|j|dS)Nrv)rxrv)rtr|r6rkwargsrs rfactoryz.ForcedIPHTTPHandler.http_open..factoryis2)+ 9-- rdo_openrreqrs` r http_openzForcedIPHTTPHandler.http_openhs2     ||GS)))r) r!r"r#r$rr3httpclient HTTPResponserrrs@rrrasj==&i&&&&&&* 8********rrcLeZdZdZdeddffd ZdejjfdZ xZ S)ForcedIPHTTPSHandlerz4urllib handler that creates ForcedIPHTTPSConnection.rxrrcht|||_||_dS)N)r)r{r3r|r)rrxrr}s rr3zForcedIPHTTPSHandler.__init__vs1 )))% rr c:fd}||S)Ncdt|jj|dS)Nrv)rxrrv)rr|rr6rs rrz0ForcedIPHTTPSHandler.https_open..factory|s7*+  9--  rrrs` r https_openzForcedIPHTTPSHandler.https_open{s2     ||GS)))r) r!r"r#r$rr3rrrrrrs@rrrssr>> i :J      *!9 * * * * * * * *rrceZdZdZdddddeededdefd Zdd d ej j d ee d e j jfdZdS) UrlTransportaSingle entrypoint for opening urllib requests. If *ip_chooser* is provided, the transport will connect to the selected IP address, while keeping correct Host/SNI/cert validation for the original hostname. If *ip_chooser* is not provided, it behaves like plain urllib. NT)rx ssl_context use_proxiesrxrrrc6ddl}|p||_g}|s2|tji|)|t|t||jgz }t jj ||_ dS)Nr)rx)rxr) sslcreate_default_context _ssl_contextr<urllibrequest ProxyHandlerrr build_opener_opener)rrxrr_sslhandlerss rr3zUrlTransport.__init__s 'H4+F+F+H+H = OOFN77;; < < <  ! #z:::$) - H~2H= rrvrrvr cr||j|S|j||S)Nr)ropen)rrrvs rrzUrlTransport.opens: ?<$$S)) )|  g 666r)r!r"r#r$rrrer3rrRequestrLrrrrr'rrrrs+/26 >>>Y'>./ >  >>>>:$( 777 ^ #7% 7  ! 777777rr)*r$ http.clientrrrTr9r0r5urllib.requestrabcrrloggingrtypingrrrr rr!r7rKr%rerrr)rNrprHTTPConnectionrtHTTPSConnectionrr HTTPHandlerr HTTPSHandlerrrr'rrrs,,\  ########777777777777JJJ 8  ! + + + + + + + +GGGGGGGGT@@@@@I@@@Fi0& & & & & T[7& & & R3 3 3 3 3 dk93 3 3 l*****&.4***$*****6>6***()7)7)7)7)7)7)7)7)7)7rdefence360agent/utils/__pycache__/net_transport.cpython-311.pyc0000644000000000000000000004700000000000000021523 0ustar r_jp0dZddlZddlZddlZddlZddlZddlZddlZ ddl m Z m Z ddl mZddlmZmZmZmZerddlZeeZdZdedefd ZGd d e ZGd d ZGddeZGddeZGddejj Z!Gddejj"Z#Gdde j$j%Z&Gdde j$j'Z(GddZ)dS)avNetworking transport helpers for urllib. This module provides a small abstraction on top of urllib.request so that callers can keep using urllib.request.Request, but routing of connections can be customized: - hostname resolution is handled in user code; - selected IP may be randomized or chosen using any complex logic; - for HTTPS: connects to a chosen IP but keeps correct SNI and certificate hostname validation for the original hostname (NOT the IP). Examples: Default behavior (plain urllib): from defence360agent.utils.net_transport import UrlTransport transport = UrlTransport() req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Randomize target IP on each connection (A/AAAA -> random choice): from defence360agent.utils.net_transport import UrlTransport, RandomIpChooser chooser = RandomIpChooser() transport = UrlTransport(ip_chooser=chooser) req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Notes: - HTTPS: connects to the chosen IP but keeps SNI/cert checks against original hostname. - HTTP: Host header stays original hostname because urllib builds it from the URL. N)ABCabstractmethod) getLogger)DictOptionalTuple TYPE_CHECKINGgr@ipreturnc~ ttj|tjS#t$rYdSwxYw)z~Return True if *ip* is an IPv4 address string. Implementation relies solely on ipaddress.ip_address for correctness. F) isinstance ipaddress ip_address IPv4Address ValueError)r s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/net_transport.py_is_ipv4rCsG ).r22I4IJJJ uus +. <<cJeZdZdZedededefdZdededefdZdS) IpChooserzSelect an IP address to connect to for a given hostname and port. Implementations may be stateful and can keep caches/metrics inside. hostnameportr ct)z6Return an IP address (v4 or v6) for *hostname*:*port*.)NotImplementedErrorselfrrs rchoosezIpChooser.chooseTs "!c.|||SN)rrs r__call__zIpChooser.__call__Ys{{8T***rN) __name__ __module__ __qualname____doc__rstrintrr rrrrNs| "s"#"#"""^"++C+C++++++rrcXeZdZdZejeddedefdZ de dede e d ffd Z d S) DnsCacheResolverzDNS cache for socket.getaddrinfo() results. It caches per (hostname, port, family). This is intentionally small and local: it is meant only to avoid excessive getaddrinfo() calls. )family ttl_secondsr*r+c`||_||_i|_tj|_dSr)_family _ttl_seconds_cache threadingLock_lock)rr*r+s r__init__zDnsCacheResolver.__init__ds4  '  ^%% rrrr .c,|||jf}tj}|j5|j|}|;|\}}||kr0t d|||j|cdddSdddn #1swxYwYt d|||jtj|||jtj }g}|D])\} } } } } | d} | |vr| | *|s#td ||t|} |j5||jz| f|j|<dddn #1swxYwYt d|||j| | S)Nz0DnsCacheResolver cache hit for %s:%s (family=%s)z9DnsCacheResolver cache miss/expired for %s:%s (family=%s)rzNo IPs resolved for {}:{}z1DnsCacheResolver resolved %s:%s (family=%s) to %s)r-timer2r/getloggerdebugsocket getaddrinfo SOCK_STREAMappendOSErrorformattupler.) rrrkeynowcached expires_atipsinfos_sockaddrr ips_ts rget_ipszDnsCacheResolver.get_ipsqsnt|,ikk Z  [__S))F!"( C##LLJ                            G   L    "   L     $)   Aq!Q!B}} 2 N5<z7RandomIpChooserWithIPv6Toggle.choose..s-::Xb\\:::::::rzNo IPv4 IPs resolved for {}:{}zKRandomIpChooserWithIPv6Toggle selected IPv4 IP %s for %s:%s (IPv6 disabled)) rSrIrWrVchoicerXr7r8r?r=r>)rrrrDchosenipv4_ipss rrz$RandomIpChooserWithIPv6Toggle.choosesn$$Xt44   Y%%c**F"DM LL!    M::c::::: 077$GG !!(++           rr N)r!r"r#r$rr)rTrUrer3r\r^r`r%rbrfr&rr'rrrNrNs04'+! , , ,+, ,fm $ ,  , , , ,""""####"""""#>$>>>>s##rrNc`eZdZdZddddeedeejfdZde de d e fd Z dS) RandomIpChooserzAResolve hostname and select a random IP from resolved candidates.N)rOrPrOrPcd|p t|_|ptj|_dSr)r)rSrTrUrV)rrOrPs rr3zRandomIpChooser.__init__s- "7%5%7%7*6=?? rrrr c|j||}|j|}td||||S)Nz(RandomIpChooser selected IP %s for %s:%s)rSrIrVrkr7r8)rrrrDrls rrzRandomIpChooser.choosesWn$$Xt44!!#&& 6        r) r!r"r#r$rr)rTrUr3r%r&rr'rrrprpsKK 04'+ ++++,+fm $ ++++ s # #      rrpcZeZdZdZ d ejdddedeede ffdZ d d Z xZ S) ForcedIPHTTPConnectionzHTTPConnection that connects to a chosen IP. Important: urllib builds the request URL with the original hostname, therefore the Host header stays correct. Ntimeoutsource_addressrr ip_chooserc`t||||||_dS)N)rrvrwsuperr3 _ip_chooser)rrrrxrvrw __class__s rr3zForcedIPHTTPConnection.__init__ sB  )    &rr c|jpd}|j|j|}td|||jt j||f|j|j |_ dS)NPz:ForcedIPHTTPConnection connecting to %s:%s for hostname %s) rr|rhostr7r8r9create_connectionrvrwsock)rrr s rconnectzForcedIPHTTPConnection.connectswyB   $ $TY 5 5 H   I    , J L     rrrn r!r"r#r$r9_GLOBAL_DEFAULT_TIMEOUTr%rr&rr3r __classcell__r}s@rrtrts#& .&&&&sm&  &&&&&&"        rrtc ^eZdZdZ d ejdddedeede ddffd Z d d Z xZ S)ForcedIPHTTPSConnectionzHTTPSConnection that connects to a chosen IP. TLS details: - Uses original hostname for SNI (server_hostname in wrap_socket) - Certificate hostname validation is performed for the original hostname Nrurrrxcontextssl.SSLContextcbt|||||||_dS)N)rrrvrwrz)rrrrxrrvrwr}s rr3z ForcedIPHTTPSConnection.__init__3sE  )    &rr c~|jpd}|j|j|}td|||jt j||f|j|j }|j r"||_ | |j }|j ||j|_ dS)Niz;ForcedIPHTTPSConnection connecting to %s:%s for hostname %s)server_hostname)rr|rrr7r8r9rrvrw _tunnel_hostr_tunnel_context wrap_socket)rrr raw_socks rrzForcedIPHTTPSConnection.connectFsyC   $ $TY 5 5 I   I    + J L       ! DI LLNNNyHM--  I.   rrrnrrs@rrr+s#&.&&&&sm&  & " &&&&&&&        rrcHeZdZdZdeffd ZdejjfdZ xZ S)ForcedIPHTTPHandlerz3urllib handler that creates ForcedIPHTTPConnection.rxcVt||_dSrrz)rrxr}s rr3zForcedIPHTTPHandler.__init__ds' %rr c:fd}||S)NcXt|j|dS)Nrv)rxrv)rtr|r6rkwargsrs rfactoryz.ForcedIPHTTPHandler.http_open..factoryis2)+ 9-- rdo_openrreqrs` r http_openzForcedIPHTTPHandler.http_openhs2     ||GS)))r) r!r"r#r$rr3httpclient HTTPResponserrrs@rrrasj==&i&&&&&&* 8********rrcLeZdZdZdeddffd ZdejjfdZ xZ S)ForcedIPHTTPSHandlerz4urllib handler that creates ForcedIPHTTPSConnection.rxrrcht|||_||_dS)N)r)r{r3r|r)rrxrr}s rr3zForcedIPHTTPSHandler.__init__vs1 )))% rr c:fd}||S)Ncdt|jj|dS)Nrv)rxrrv)rr|rr6rs rrz0ForcedIPHTTPSHandler.https_open..factory|s7*+  9--  rrrs` r https_openzForcedIPHTTPSHandler.https_open{s2     ||GS)))r) r!r"r#r$rr3rrrrrrs@rrrssr>> i :J      *!9 * * * * * * * *rrceZdZdZdddddeededdefd Zdd d ej j d ee d e j jfdZdS) UrlTransportaSingle entrypoint for opening urllib requests. If *ip_chooser* is provided, the transport will connect to the selected IP address, while keeping correct Host/SNI/cert validation for the original hostname. If *ip_chooser* is not provided, it behaves like plain urllib. NT)rx ssl_context use_proxiesrxrrrc6ddl}|p||_g}|s2|tji|)|t|t||jgz }t jj ||_ dS)Nr)rx)rxr) sslcreate_default_context _ssl_contextr<urllibrequest ProxyHandlerrr build_opener_opener)rrxrr_sslhandlerss rr3zUrlTransport.__init__s 'H4+F+F+H+H = OOFN77;; < < <  ! #z:::$) - H~2H= rrvrrvr cr||j|S|j||S)Nr)ropen)rrrvs rrzUrlTransport.opens: ?<$$S)) )|  g 666r)r!r"r#r$rrrer3rrRequestrLrrrrr'rrrrs+/26 >>>Y'>./ >  >>>>:$( 777 ^ #7% 7  ! 777777rr)*r$ http.clientrrrTr9r0r5urllib.requestrabcrrloggingrtypingrrrr rr!r7rKr%rerrr)rNrprHTTPConnectionrtHTTPSConnectionrr HTTPHandlerr HTTPSHandlerrrr'rrrs,,\  ########777777777777JJJ 8  ! + + + + + + + +GGGGGGGGT@@@@@I@@@Fi0& & & & & T[7& & & R3 3 3 3 3 dk93 3 3 l*****&.4***$*****6>6***()7)7)7)7)7)7)7)7)7)7rdefence360agent/utils/__pycache__/parsers.cpython-311.opt-1.pyc0000644000000000000000000004160300000000000021242 0ustar r_jg.$ddlZddlZddlZddlmZmZddlmZddlm Z m Z m Z m Z m Z mZddlmZddlmZddlmZddlmZdd lmZGd d Zd ZGd dZdZdZdZdZ dZ!dZ"eddZ#dZ$dS)N) lru_cachepartial)chain)AnyDictIterableIteratorMappingTuple)app)Core)prepare_schema) RpcClient)EXITCODE_NOT_FOUNDceZdZeeeefZdZe defdZ e dZ defdZ defdZ defdZdefdZdefd Zd Zd S) SchemaToArgparsec||_|d|_|d|_|dd|_|d|_|dd|_|d|_|dd|_|d |_ dS) NalloweddefaultenvvarFhelp positionalrenamerequiredtype) _argumentget_allowed_default_envvar_help _positional_rename _required_type)selfargumentoptionss R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/parsers.py__init__zSchemaToArgparse.__init__s&")++i"8"8 $[[33 #KK%88 !++f-- !(\5!A!A#KK11 &{{:u==!++f-- returncZ|jr|jSd|jddzS)N--_-)r"rreplacer&s r)argnamezSchemaToArgparse.argnames2   "> !dn,,S#6666r+c  tt||||||}|SN)dictrchoicesrrmetavarnargsr)r&argparse_optionss r)r(zSchemaToArgparse.options%si              r+c#Kd}|jdkr%|js|jr|js|dfVdS|dfVdS|jr|js|j |dfVdSdSdS)Nr9list+*?)r%r"r$r rr&options r)r9zSchemaToArgparse.nargs3s :  # " "t| "ck!!!!!ck!!!!!   4< 4=3H#+       3H3Hr+c# Kd|jfVdS)Nr7)rr2s r)r7zSchemaToArgparse.choices?s&&&&&&r+c# Kd|jfVdS)Nr)r!r2s r)rzSchemaToArgparse.helpBsdj      r+c#Kd}|jr||jfVdS|jdkr||jfVdSdS)Nr8r<)r#upperr%rr@s r)r8zSchemaToArgparse.metavarEst < 1$,,,... . . . . . Z6 ! !$...000 0 0 0 0 0" !r+c#lK|j&|js!|jdks|jsd|jfVdSdSdSdS)Nr<r)rr r%r"r2s r)rzSchemaToArgparse.defaultLsZ M %L &v%%T-=%T]* * * * * * & % % %%%r+c#bK|jr|jdkr|js|js dVdSdSdSdSdS)Nr<)rT)r$r%r r"r2s r)rzSchemaToArgparse.requiredTsd N # f$$L%$% # " " " " " # #$$$$$$r+N)__name__ __module__ __qualname__r r strr OptionTyper*propertyr3r(r9r7rr8rrr+r)rrs %S/*J . . .7777X7    X   z    '''''!j!!!!11111+++++#####r+rc|ddkr|do|dd }||}|d|dd z|d |d |dd z|d |jdi||didSt ||}|j|jfi|jdS)NrbooleanrrF)rr.r/r0 store_true)destactionz--no- store_falserrN)radd_mutually_exclusive_group add_argumentr1 set_defaultsrr3r()parserr'r(r bool_parser converters r)schema_to_argparser[_s6{{6i'';;z**O7;;x3O3O/O9989LL   8##C-- - !     h&&sC00 0  !   !  FFHgkk).D.D#EFFFFF$Xw77 I-CC1BCCCCCr+c eZdZedefdZedZededee de e e ffdZ edZ d S) EnvParserenvvar_parameter_optionsc|sdSdddfd|DS)Nc@d|vr|dd|dS|dS)Nrrz rN)r(s r) format_argz)EnvParser.format_help..format_argys5  !(+BBBBB8$ $r+z environment variables: {}z c3.K|]}|VdSr5rN).0r(rbs r) z(EnvParser.format_help..sA 7##r+)formatjoinvalues)r^rbs @r) format_helpzEnvParser.format_helpts|' 2 % % % 177 KK7>>@@     r+cpd|vr1 |dn#t$r d|d|dfcYSwxYwdS)Nisasciiasciizerror: =z must only contain ascii symbols)encodeUnicodeEncodeError)rvaluer(s r) _validatezEnvParser._validatess     W%%%%%   NfNNuNNN ts 33environexcluder,c li}|D]\}}||vr |d} ||x} ||<||| |x} rK|||| } t| tjt jt#t$rd|vr |d||<Y| dsY|||d |} t| tjt jtYwxYw|S)Nr)filerrz-error: environment variable {} is not defined) itemsrq _format_errorprintsysstderrexitrKeyErrorrrf) clsrrcommandr^rskwargs parameterr( envvar_namerperrmsgs r)parsezEnvParser.parsesz":"@"@"B"B 1 1 IwG##!(+K 1,3K,@@y)"-- UGDDD31++!93C#CJ////H/000+ - - -''(/ (:F9%H{{:..H'',CJJ#c +++++,,,,, -, s BD18D1AD10D1c~dd||||S)Nz{command}: {help} {message} )r~rmessage)rfrgri)r}r~r^rs r)rwzEnvParser._format_errorsA077HHW%%!9::8   r+N) rHrIrJ staticmethodr rirq classmethodrrKrrrwrNr+r)r]r]ss g   \ \$$ # $ c3h $$$[$L  [   r+r]c^ tj|n#tj$rYdSwxYwdS)NFT) ipaddress IPv4AddressAddressValueError)addrs r)is_valid_ipv4_addrrsEd####  &uu 4s **c#K|D]7\}}||didgvr||fV8dS)Ncliusers)rvr)schemauserkeyrhs r) _filter_userrse||~~ V 6::eR((,,Wb99 9 9v+   r+c >t|j|di|S)N)require_svc_is_runningrN)rcmd)r~ require_rpcparamss r) rpc_endpointrs9 F <9K 8 8 8 .sT'\Bg>S\\>1B&Br+Available commandsr)rrT)rrzparser is not definedrr envvar_onlyFrrrz--jsonrQzreturn data in JSON format)rSrz --verbosez-vcount)rSrrrunning)r)endpointrr^r~rN)sortedkeys isinstancetupler< enumerater add_parserrargparseRawDescriptionHelpFormatteranyadd_subparsersrvupdater[r]riepilogrVrWrrr) subparsersr _subparserscommandsrhrX subparserir~hashableexists_subparserr^r'r(rrs @r) apply_parserrsyKfkkmm$$HP P 'E4=11111 #G,," 1" 1JAwCLL1$$$"-- F++$,$H. %,2+@+@1,A,,K(!1q5!122#.??8#<#< '18A8L8L$#ZZ//9M99%n*>nNNOI H 5 5 !1II.....v$& !'Hb!9!9!?!?!A!A : : Hg7""5<(2;;}e447""EE!1'(2C!DEEE&+ #(- % vx 9 9 9 9!--.FGG  \0L     Kg>>>jj++// yII  \7K@@%,( **Xr227799&&&&>  GP P r+cttttj|}t ||dSr5)r6rrr SCHEMA_PATHSr)rrrs r)_apply_subparsersr5s: ,~c.>??FF G GFV$$$$$r+r)maxsizecHtjdtjz}|dd|dgdd |d d d |d}t |dt||S)Nz CLI for %s.) descriptionz --log-configzlogging config filenamerz--console-log-level)ERRORWARNINGINFODEBUGz%Level of logging input to the consoler7rz --remote-addrc(t|r|ndSr5)r)ips r)z#create_cli_parser..Fs044>$r+z2Client's IP address for adding it to the whitelist)rrrroot)rArgumentParserConfigNAMErVrr_apply_completions_parser)rXrs r)create_cli_parserr:s  $1L M M MF -FGGG 555 4   > > A &&,@&AAJj&)))j))) Mr+cddlm}|dd}|d|d|d dS) Nr)SUPPORTED_SHELLS completionsz&Generate shell auto-completion scriptsrshellz!Shell to generate completions forrT)completions_command)!defence360agent.utils.completionsrrrVrW)rrcompletions_parsers r)rrOs|BBBBBB#.. 5/## 0$ ###=====r+)%rrry functoolsrr itertoolsrtypingrrrr r r defence360agent.applicationr defence360agent.contracts.configr rdefence360agent.rpc_tools.utilsrdefence360agent.simple_rpcrdefence360agent.utils.clirrr[r]rrrrrrrrrNr+r)rs ((((((((@@@@@@@@@@@@@@@@++++++;;;;;;::::::000000888888M#M#M#M#M#M#M#M#`DDD(J J J J J J J J Z  S S S l%%%  1( > > > > >r+defence360agent/utils/__pycache__/parsers.cpython-311.pyc0000644000000000000000000004160300000000000020303 0ustar r_jg.$ddlZddlZddlZddlmZmZddlmZddlm Z m Z m Z m Z m Z mZddlmZddlmZddlmZddlmZdd lmZGd d Zd ZGd dZdZdZdZdZ dZ!dZ"eddZ#dZ$dS)N) lru_cachepartial)chain)AnyDictIterableIteratorMappingTuple)app)Core)prepare_schema) RpcClient)EXITCODE_NOT_FOUNDceZdZeeeefZdZe defdZ e dZ defdZ defdZ defdZdefdZdefd Zd Zd S) SchemaToArgparsec||_|d|_|d|_|dd|_|d|_|dd|_|d|_|dd|_|d |_ dS) NalloweddefaultenvvarFhelp positionalrenamerequiredtype) _argumentget_allowed_default_envvar_help _positional_rename _required_type)selfargumentoptionss R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/parsers.py__init__zSchemaToArgparse.__init__s&")++i"8"8 $[[33 #KK%88 !++f-- !(\5!A!A#KK11 &{{:u==!++f-- returncZ|jr|jSd|jddzS)N--_-)r"rreplacer&s r)argnamezSchemaToArgparse.argnames2   "> !dn,,S#6666r+c  tt||||||}|SN)dictrchoicesrrmetavarnargsr)r&argparse_optionss r)r(zSchemaToArgparse.options%si              r+c#Kd}|jdkr%|js|jr|js|dfVdS|dfVdS|jr|js|j |dfVdSdSdS)Nr9list+*?)r%r"r$r rr&options r)r9zSchemaToArgparse.nargs3s :  # " "t| "ck!!!!!ck!!!!!   4< 4=3H#+       3H3Hr+c# Kd|jfVdS)Nr7)rr2s r)r7zSchemaToArgparse.choices?s&&&&&&r+c# Kd|jfVdS)Nr)r!r2s r)rzSchemaToArgparse.helpBsdj      r+c#Kd}|jr||jfVdS|jdkr||jfVdSdS)Nr8r<)r#upperr%rr@s r)r8zSchemaToArgparse.metavarEst < 1$,,,... . . . . . Z6 ! !$...000 0 0 0 0 0" !r+c#lK|j&|js!|jdks|jsd|jfVdSdSdSdS)Nr<r)rr r%r"r2s r)rzSchemaToArgparse.defaultLsZ M %L &v%%T-=%T]* * * * * * & % % %%%r+c#bK|jr|jdkr|js|js dVdSdSdSdSdS)Nr<)rT)r$r%r r"r2s r)rzSchemaToArgparse.requiredTsd N # f$$L%$% # " " " " " # #$$$$$$r+N)__name__ __module__ __qualname__r r strr OptionTyper*propertyr3r(r9r7rr8rrr+r)rrs %S/*J . . .7777X7    X   z    '''''!j!!!!11111+++++#####r+rc|ddkr|do|dd }||}|d|dd z|d |d |dd z|d |jdi||didSt ||}|j|jfi|jdS)NrbooleanrrF)rr.r/r0 store_true)destactionz--no- store_falserrN)radd_mutually_exclusive_group add_argumentr1 set_defaultsrr3r()parserr'r(r bool_parser converters r)schema_to_argparser[_s6{{6i'';;z**O7;;x3O3O/O9989LL   8##C-- - !     h&&sC00 0  !   !  FFHgkk).D.D#EFFFFF$Xw77 I-CC1BCCCCCr+c eZdZedefdZedZededee de e e ffdZ edZ d S) EnvParserenvvar_parameter_optionsc|sdSdddfd|DS)Nc@d|vr|dd|dS|dS)Nrrz rN)r(s r) format_argz)EnvParser.format_help..format_argys5  !(+BBBBB8$ $r+z environment variables: {}z c3.K|]}|VdSr5rN).0r(rbs r) z(EnvParser.format_help..sA 7##r+)formatjoinvalues)r^rbs @r) format_helpzEnvParser.format_helpts|' 2 % % % 177 KK7>>@@     r+cpd|vr1 |dn#t$r d|d|dfcYSwxYwdS)Nisasciiasciizerror: =z must only contain ascii symbols)encodeUnicodeEncodeError)rvaluer(s r) _validatezEnvParser._validatess     W%%%%%   NfNNuNNN ts 33environexcluder,c li}|D]\}}||vr |d} ||x} ||<||| |x} rK|||| } t| tjt jt#t$rd|vr |d||<Y| dsY|||d |} t| tjt jtYwxYw|S)Nr)filerrz-error: environment variable {} is not defined) itemsrq _format_errorprintsysstderrexitrKeyErrorrrf) clsrrcommandr^rskwargs parameterr( envvar_namerperrmsgs r)parsezEnvParser.parsesz":"@"@"B"B 1 1 IwG##!(+K 1,3K,@@y)"-- UGDDD31++!93C#CJ////H/000+ - - -''(/ (:F9%H{{:..H'',CJJ#c +++++,,,,, -, s BD18D1AD10D1c~dd||||S)Nz{command}: {help} {message} )r~rmessage)rfrgri)r}r~r^rs r)rwzEnvParser._format_errorsA077HHW%%!9::8   r+N) rHrIrJ staticmethodr rirq classmethodrrKrrrwrNr+r)r]r]ss g   \ \$$ # $ c3h $$$[$L  [   r+r]c^ tj|n#tj$rYdSwxYwdS)NFT) ipaddress IPv4AddressAddressValueError)addrs r)is_valid_ipv4_addrrsEd####  &uu 4s **c#K|D]7\}}||didgvr||fV8dS)Ncliusers)rvr)schemauserkeyrhs r) _filter_userrse||~~ V 6::eR((,,Wb99 9 9v+   r+c >t|j|di|S)N)require_svc_is_runningrN)rcmd)r~ require_rpcparamss r) rpc_endpointrs9 F <9K 8 8 8 .sT'\Bg>S\\>1B&Br+Available commandsr)rrT)rrzparser is not definedrr envvar_onlyFrrrz--jsonrQzreturn data in JSON format)rSrz --verbosez-vcount)rSrrrunning)r)endpointrr^r~rN)sortedkeys isinstancetupler< enumerater add_parserrargparseRawDescriptionHelpFormatteranyadd_subparsersrvupdater[r]riepilogrVrWrrr) subparsersr _subparserscommandsrhrX subparserir~hashableexists_subparserr^r'r(rrs @r) apply_parserrsyKfkkmm$$HP P 'E4=11111 #G,," 1" 1JAwCLL1$$$"-- F++$,$H. %,2+@+@1,A,,K(!1q5!122#.??8#<#< '18A8L8L$#ZZ//9M99%n*>nNNOI H 5 5 !1II.....v$& !'Hb!9!9!?!?!A!A : : Hg7""5<(2;;}e447""EE!1'(2C!DEEE&+ #(- % vx 9 9 9 9!--.FGG  \0L     Kg>>>jj++// yII  \7K@@%,( **Xr227799&&&&>  GP P r+cttttj|}t ||dSr5)r6rrr SCHEMA_PATHSr)rrrs r)_apply_subparsersr5s: ,~c.>??FF G GFV$$$$$r+r)maxsizecHtjdtjz}|dd|dgdd |d d d |d}t |dt||S)Nz CLI for %s.) descriptionz --log-configzlogging config filenamerz--console-log-level)ERRORWARNINGINFODEBUGz%Level of logging input to the consoler7rz --remote-addrc(t|r|ndSr5)r)ips r)z#create_cli_parser..Fs044>$r+z2Client's IP address for adding it to the whitelist)rrrroot)rArgumentParserConfigNAMErVrr_apply_completions_parser)rXrs r)create_cli_parserr:s  $1L M M MF -FGGG 555 4   > > A &&,@&AAJj&)))j))) Mr+cddlm}|dd}|d|d|d dS) Nr)SUPPORTED_SHELLS completionsz&Generate shell auto-completion scriptsrshellz!Shell to generate completions forrT)completions_command)!defence360agent.utils.completionsrrrVrW)rrcompletions_parsers r)rrOs|BBBBBB#.. 5/## 0$ ###=====r+)%rrry functoolsrr itertoolsrtypingrrrr r r defence360agent.applicationr defence360agent.contracts.configr rdefence360agent.rpc_tools.utilsrdefence360agent.simple_rpcrdefence360agent.utils.clirrr[r]rrrrrrrrrNr+r)rs ((((((((@@@@@@@@@@@@@@@@++++++;;;;;;::::::000000888888M#M#M#M#M#M#M#M#`DDD(J J J J J J J J Z  S S S l%%%  1( > > > > >r+defence360agent/utils/__pycache__/resource_limits.cpython-311.opt-1.pyc0000644000000000000000000002263000000000000022772 0ustar r_j& ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z m Z m Z ddlmZejeZdZe d Ze d Ze d Ze d Zd ZGddeZdefdZde ededededejjf dZdedede efdZ dede efdZ!dede efdZ"dede efdZ#de de efdZ$dede e eeffdZ%de&fd Z'de&fd!Z(dS)"N)suppress)Enum)fsdecode)Path)ListOptionalTuple) OsReleaseInfoz/usr/libexec/run-with-intensityz/usr/sbin/lvectlz/proc/lve/listz/procz/sys/fs/cgroupl ceZdZdZdZdZdS) LimitsMethodnicelvecgroupsN)__name__ __module__ __qualname__NICELVECGROUPSZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/resource_limits.pyr r s D CGGGrr returncKtjtdtjjtjjd{V}|d{V\}}t |}|dkr tj S|dkr tj S|dkr tj Std |t |)z6Returns limit method, used in run-with-intensity tool.show)stdoutstderrNr rrz>Parsing of used limitation method failed stdout: {} stderr: {})asynciocreate_subprocess_execRUN_WITH_INTENSITY subprocessPIPE communicaterstripr rrr LookupErrorformat)procrrs rget_current_methodr( s/!&!&       D ++--------NFF f   # # % %F     ## J ((..00 1 1  rcmdkey intensity_cpu intensity_iocKtddt|dt|g}|d|gtj||zi|d{VS)aS Creates asyncio.Process with limited resources (cpu & io), using run-with-intensity tool. :param cmd: command to execute :param intensity_cpu: cpu intensity limit :param intensity_io: io intensity limit :param subprocess_kwargs: keyword arguments for create_subprocess_exec func :return: executed Process runz--intensity-cpuz--intensity-ioz--keyN)r strextendrr)r)r*r+r,subprocess_kwargs limits_cmds rcreate_subprocessr38s$   M L Jwn%%%/ s 0      rpidfieldc tt|z dz }n#t$rYdSwxYw|D]|}|d\}}}||kr[t tt5t| dcdddcS#1swxYwY}dS)Nstatus:r) PROC_PATHr/ read_textOSError splitlines partitionr IndexError ValueErrorintsplit)r4r5r7linename_values r_status_field_kbrFXsc#hh&1<<>> tt!!##--,,a 5==*j11 - -5;;==+,, - - - - - - - - - - - - - - - - - - 4s,/ =='CC C cg} ttt|z dz }n#t$r|cYSwxYw|D]} |dz }n#t$rY'wxYwt t5|d| Ddddn #1swxYwY|S)Ntaskchildrenc34K|]}t|VdSN)r@).0childs r z_child_pids..qs(CC5CJJCCCCCCr) sortedr9r/iterdirr;r:rr?r0rA)r4rIthreadsthreadlisteds r _child_pidsrTesLH)c#hh.7@@BBCC DD z)4466FF    H  j ! ! D D OOCCFLLNNCCC C C C D D D D D D D D D D D D D D D Os39> A  A A-- A:9A:2CC C cg|g}}|rM|}|||t||M|SrK)popappendr0rT)r4treependingcurrents r _process_treer[us_'D -++-- G{7++,,, - Krc`d}t|D]}t|d}||pd|z}|S)z;Peak RSS of the process and its descendants, summed, in kB.NVmHWMr)r[rF)r4totalprocesspeaks r peak_rss_kbra~sG E %%((11  Za4'E Lrpathc2 |}n#t$rYdSwxYw|dkrdStt5t |}|t kr|cdddS dddn #1swxYwYdS)Nmax)r:r$r;rr?r@_CGROUP_V1_NO_LIMIT)rbrElimits r_cgroup_limit_bytesrgs  &&(( tt ~~t *  E  & & & & 4s&) 77B  BBc tt|z dz }n#t$rYdSwxYw|D]}|d\}}}|d\}}}|d}|stt|z dz }d} n8d| dvr ttdz |z d z }d } n| |d z| fcSdS) zFThe cgroup memory limit the process runs under, in kB, and its source.cgroupNr8/z memory.maxz cgroup v2memory,zmemory.limit_in_bytesz cgroup v1i) r9r/r:r;r<r=lstriprg CGROUP_PATHrA) r4rrBrDrest controllersrirelativerfsources rmemory_bound_kbrss9s3xx'(2==?? tt""$$))^^C(( 1d!%!4!4 Q==%% ' h(>(MNNE FF **3// / /'h&14KKE!FF   D=&( ( ( (  4s ,/ ==cZtotjS)z1Checks that LVE-utils is active resource limiter.)PROC_LVE_LIST_PATHexistsr is_cloudlinuxrrr is_lve_activerxs$  $ $ & & H=+F+H+HHrc4tS)z#Checks that LVE-utils is installed.)LVECTL_BIN_PATHrvrrr has_lvectlr{s  ! ! # ##r))rlogging contextlibrenumrosrpathlibrtypingrrr defence360agent.utilsr getLoggerrloggerr rzrur9rnrer r(r/r@r!Processr3rFrTr[rargrsboolrxr{rrrrs((((((((((//////  8 $ $7$)**T*++ DMM d#$$ 4 ,0 c     @ # c hsm     S T#Y     stCySXc] d x}    %S/!:2ItIIII$D$$$$$$rdefence360agent/utils/__pycache__/resource_limits.cpython-311.pyc0000644000000000000000000002263000000000000022033 0ustar r_j& ddlZddlZddlmZddlmZddlmZddlm Z ddl m Z m Z m Z ddlmZejeZdZe d Ze d Ze d Ze d Zd ZGddeZdefdZde ededededejjf dZdedede efdZ dede efdZ!dede efdZ"dede efdZ#de de efdZ$dede e eeffdZ%de&fd Z'de&fd!Z(dS)"N)suppress)Enum)fsdecode)Path)ListOptionalTuple) OsReleaseInfoz/usr/libexec/run-with-intensityz/usr/sbin/lvectlz/proc/lve/listz/procz/sys/fs/cgroupl ceZdZdZdZdZdS) LimitsMethodnicelvecgroupsN)__name__ __module__ __qualname__NICELVECGROUPSZ/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/resource_limits.pyr r s D CGGGrr returncKtjtdtjjtjjd{V}|d{V\}}t |}|dkr tj S|dkr tj S|dkr tj Std |t |)z6Returns limit method, used in run-with-intensity tool.show)stdoutstderrNr rrz>Parsing of used limitation method failed stdout: {} stderr: {})asynciocreate_subprocess_execRUN_WITH_INTENSITY subprocessPIPE communicaterstripr rrr LookupErrorformat)procrrs rget_current_methodr( s/!&!&       D ++--------NFF f   # # % %F     ## J ((..00 1 1  rcmdkey intensity_cpu intensity_iocKtddt|dt|g}|d|gtj||zi|d{VS)aS Creates asyncio.Process with limited resources (cpu & io), using run-with-intensity tool. :param cmd: command to execute :param intensity_cpu: cpu intensity limit :param intensity_io: io intensity limit :param subprocess_kwargs: keyword arguments for create_subprocess_exec func :return: executed Process runz--intensity-cpuz--intensity-ioz--keyN)r strextendrr)r)r*r+r,subprocess_kwargs limits_cmds rcreate_subprocessr38s$   M L Jwn%%%/ s 0      rpidfieldc tt|z dz }n#t$rYdSwxYw|D]|}|d\}}}||kr[t tt5t| dcdddcS#1swxYwY}dS)Nstatus:r) PROC_PATHr/ read_textOSError splitlines partitionr IndexError ValueErrorintsplit)r4r5r7linename_values r_status_field_kbrFXsc#hh&1<<>> tt!!##--,,a 5==*j11 - -5;;==+,, - - - - - - - - - - - - - - - - - - 4s,/ =='CC C cg} ttt|z dz }n#t$r|cYSwxYw|D]} |dz }n#t$rY'wxYwt t5|d| Ddddn #1swxYwY|S)Ntaskchildrenc34K|]}t|VdSN)r@).0childs r z_child_pids..qs(CC5CJJCCCCCCr) sortedr9r/iterdirr;r:rr?r0rA)r4rIthreadsthreadlisteds r _child_pidsrTesLH)c#hh.7@@BBCC DD z)4466FF    H  j ! ! D D OOCCFLLNNCCC C C C D D D D D D D D D D D D D D D Os39> A  A A-- A:9A:2CC C cg|g}}|rM|}|||t||M|SrK)popappendr0rT)r4treependingcurrents r _process_treer[us_'D -++-- G{7++,,, - Krc`d}t|D]}t|d}||pd|z}|S)z;Peak RSS of the process and its descendants, summed, in kB.NVmHWMr)r[rF)r4totalprocesspeaks r peak_rss_kbra~sG E %%((11  Za4'E Lrpathc2 |}n#t$rYdSwxYw|dkrdStt5t |}|t kr|cdddS dddn #1swxYwYdS)Nmax)r:r$r;rr?r@_CGROUP_V1_NO_LIMIT)rbrElimits r_cgroup_limit_bytesrgs  &&(( tt ~~t *  E  & & & & 4s&) 77B  BBc tt|z dz }n#t$rYdSwxYw|D]}|d\}}}|d\}}}|d}|stt|z dz }d} n8d| dvr ttdz |z d z }d } n| |d z| fcSdS) zFThe cgroup memory limit the process runs under, in kB, and its source.cgroupNr8/z memory.maxz cgroup v2memory,zmemory.limit_in_bytesz cgroup v1i) r9r/r:r;r<r=lstriprg CGROUP_PATHrA) r4rrBrDrest controllersrirelativerfsources rmemory_bound_kbrss9s3xx'(2==?? tt""$$))^^C(( 1d!%!4!4 Q==%% ' h(>(MNNE FF **3// / /'h&14KKE!FF   D=&( ( ( (  4s ,/ ==cZtotjS)z1Checks that LVE-utils is active resource limiter.)PROC_LVE_LIST_PATHexistsr is_cloudlinuxrrr is_lve_activerxs$  $ $ & & H=+F+H+HHrc4tS)z#Checks that LVE-utils is installed.)LVECTL_BIN_PATHrvrrr has_lvectlr{s  ! ! # ##r))rlogging contextlibrenumrosrpathlibrtypingrrr defence360agent.utilsr getLoggerrloggerr rzrur9rnrer r(r/r@r!Processr3rFrTr[rargrsboolrxr{rrrrs((((((((((//////  8 $ $7$)**T*++ DMM d#$$ 4 ,0 c     @ # c hsm     S T#Y     stCySXc] d x}    %S/!:2ItIIII$D$$$$$$rdefence360agent/utils/__pycache__/safe_fileops.cpython-311.opt-1.pyc0000644000000000000000000004413700000000000022227 0ustar r_jP' @UddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z m Z ddlmZddlmZmZmZddlmZejZejejzejzZejeZeZ ee e!d<dej"fd Z#d)d Z$ej%e$d Z&Gd de'Z(de)d dfdZ*dZ+d*dZ,de)fdZ-de)de)fdZ.de)de)fdZ/de)fdZ0e,ej1Z1e,ej2Z2e d+dZ3e dZ4e de)fdZ5e d,dee)e6ffd Z7e de)d!e6d"e6d#e8fd$Z9d%eee)e6fee6dffd&eee)e6fee6dfffd'Z: d-d%e)d&e)fd(Z;dS).N)ProcessPoolExecutor)contextmanagersuppress)chain)SetTupleUnion)utils _active_poolsloopcKtd}t| |j|g|Rd{V |dt|S#t|wxYw# |dt|w#t|wxYwxYw)N) max_workersF)wait)rr addrun_in_executorshutdowndiscard)r argspools W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_fileops.py_run_in_fresh_executorrs 1 - - -Dd()T)$66666666666 ( MMuM % % %  ! !$ ' ' ' 'M ! !$ ' ' ' ' ( MMuM % % %  ! !$ ' ' ' 'M ! !$ ' ' ' 's/BA44BC%C+C%C""C%returncttD]L} |dd#t$r%}td|Yd}~Ed}~wwxYwtdS)zShutdown all tracked ProcessPoolExecutors. Should be called during agent shutdown to ensure clean process termination. FT)rcancel_futuresz+Error shutting down ProcessPoolExecutor: %sN)listr r Exceptionloggerwarningclear)res rshutdown_process_poolsr")s ]##MM M MMuTM : : : : M M M NNH! L L L L L L L L Ms0 AAActjgtj|tj|||SN)os setgroupssetgidsetuid)funuidgidrs rdropr,:s8LIcNNNIcNNN 3:ceZdZdS)UnsafeFileOperationN)__name__ __module__ __qualname__r-rr/r/AsDr-r/pathctj|}tj|jsYt d|tj|jtj|td|dS)zVerify path is a regular file; remove and raise FileNotFoundError if not. Uses os.lstat() to avoid following symlinks. If the file is a FIFO, symlink, socket, device, etc., it is deleted so the caller can recreate it as a regular file. z:Identity file %s is not a regular file (mode=%s), removingz#Removed non-regular identity file: N) r%lstatstatS_ISREGst_moderrfilemodeunlinkFileNotFoundError)r4sts rensure_regular_filer>Es $B < # #N H  M"* % %   $ Ld L LMMMNNr-ctjt|}|jt jkrt dt|zdS)Nz The file belongs to admin user: T)r%r7strst_uidr get_min_uidr/)filer=s rcheck_non_admin_filerDWsT T  B y5$&&&&! .T :    4r-Fcfd}|S)NcPtjddfd }|S)N)r cBKtj|s std|zt j|}t t|j|g} rt|j}|D]I}tj t|}|j dkr|j dkr|j |j }}n Jtdt|z|ptj}t!|t" |||g|Rd{VS)NzNo such file or directory: rz"Unsafe file operation under root: )r%r4existsr<pathlibPathrreversedparentsr7r@rAst_gidr/asyncioget_event_looprr,) filenamer rr4pathspr=r*r+r) missing_oks rwrapperz$safe.._safe..wrapperbsQ7>>(++ J '1H<<))D(4<004&99E / ..  WSVV__9>>bi1nn!y")CE)83t99D37133D/  r-) functoolswraps)r)rTrSs` r_safezsafe.._safeasK   04          >r-r3)rSrWs` rsaferX`s$!!!!!F Lr-rPcRtj|dSr$)rIrJtouchrPs r_touchr\s$ L  """""r-datacTtj||dSr$)rIrJ write_textrPr]s r _write_textras& L%%d+++++r-cbKtdt||d{VSNT)rS)rXrar`s rr_r_s@3&&&&{33HdCC C C C C C CCr-c`Ktdt|d{VSrc)rXr\r[s rrZrZs>.&&&&v..x88 8 8 8 8 8 88r-Tc#LKd|vrtdt||5}tj|}t j|}tjd|}t|}||ks|j |j krtd||rEtj |j tj |jvrtd|d|VddddS#1swxYwYdS)Nwz'w' mode is not permittedz/proc/self/fd/zUnable to safely read z. File is not in user homedir)r/openr%fstatfilenopwdgetpwnamreadlinkr@rApw_uidrIrJpw_dirrL) rPmodeuserrespect_homedirfr=passwd real_path filename_strs rsafe_open_filervsz d{{!"=>>> h   Xahhjj ! !d##K = = =>> 8}} I % %29 +E+E%&M|&M&MNN N   V]++< --566&.... -sC&DD Dc/BKtj|i|} |Vtt5tj|ddddS#1swxYwYdS#tt5tj|dddw#1swxYwYwxYw)z Context manager which wraps os.open and close file descriptor at the end :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open N)r%rgrOSErrorclose)rkwargsfds ropen_fdr|s! $ !& ! !B g     HRLLL                  Xg     HRLLL                s@AAAAB1B BB BB Bnamec/Kt|g|Rdtji|5}tjd|}||krt d|VddddS#1swxYwYdS)a  Context manager to get a directory file descriptor It also checks if a directory doesn't contain a symlink in the path :param name: full directory name :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open flagsz/proc/self/fd/{}z%Operations on symlinks are prohibitedN)r|r% O_DIRECTORYrlformatr/)r}rrzdir_fdreals r opendir_fdrs  = = = =BN =f = ={-44V<<== 4<<%&MNN N sAA--A14A1rrc #Kd}t|trtt5t j||}t j||jt jzt j z|dddn #1swxYwYt j |||}t||5}|pt j||_ |V|rGtt5t j||jdddn #1swxYwYnO#|rHtt5t j||jdddw#1swxYwYwwxYwddddS#1swxYwYdS)a Context manager to open file object from file name or from file descriptor File object extended with 'st' attribute that contains os.stat_result of the opened file :param f: file name or file descriptor to open :param dir_fd: directory descriptor, ignored if 'f' is a file descriptor :param flags: flags for os.open, ignored if 'f' is a file descriptor :param mode: mode for built-in open Nr)ror)rrro) isinstancer@rrxr%r7chmodr9S_IRUSRS_IWUSRrgr=)rrrrror=fos r open_fobjrs B!S 3 g    6***B H T\1DL@                    GAU6 2 2 2 ad   1r bgajj 1HHH 1g&&11HQRZ0000111111111111111 1g&&11HQRZ00001111111111111111 1 111111111111111111sA BB B :FD%F1D FD F D !F%E1<E$  E1$E( (E1+E( ,E11FFFrris_safec#K|r3t|||5}|dfVddddS#1swxYwYdS||fVdS)z If is_safe flag is True, open file descriptor using name and dir_fd If is_safe is False, return name and dir_fd as is )rrN)r|)r}rrrr{s r safe_tuplers  T& 6 6 6 "d(NNN                  Fls +//srcdstc\|\}}|\}}t|rdn tjz} t||td5} t||| d5} |r|dt j| | t|tr2tj | | j j dddn #1swxYwY|r=t|tr(|r|dtj ||ddddS#1swxYwYdS)Nrrb)rrrowbrrr)W_FLAGSr%O_EXCLrR_FLAGSshutil copyfileobjrr@rrir=r9r;) rr src_unlink dst_overwriteracecallsrc_f src_dir_fddst_f dst_dir_fdw_flagssrc_fodst_fos r_mover sE:E:m:;G  jd   0   *G$   B    vv . . .%%% Bvy/@AAAA B B B B B B B B B B B B B B B  0*UC00 0  IeJ / / / /%000000000000000000s7D!A/C > D! C D!C AD!!D%(D%czKtj|\}}tj|\} } t|5} t| 5} t || t |5} t | | t |5}tj|| }tj }t|tt|j |j| |||| d{V|r*|r(|r|dtj|| |r>tj| |j |j| tj| |j| dddn #1swxYwYdddn #1swxYwYdddn #1swxYwYddddS#1swxYwYdS)Nrr)r%r4splitrrrrr7rNrOrr,rrArMr;chownrr9)rrsafe_srcsafe_dstrrrsrc_dirsrc_namedst_dirdst_namerr src_tuple dst_tuplesrc_str s r safe_mover.s c**GX c**GX G  B J--B Z*gxB J*gx B *555%''$    M M          3( 3  Ihz 2 2 2 2  B HXv}fmJ O O O O HXv~j A A A A=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBsF0&F>FCE*  F*E. .F1E. 2F5 FF FF F F0F F0F F00F47F4)rN)F)T)NrN)FFTFN)rDrXr\rar_rZrr;rvr|rintrboolrrrr3r-rrs   222222////////$$$$$$$$$$!!!!!! + *rz !BK /  8 $ $ +.#%% s&'/// (w'@ ( ( ( (    &'''     )   NcNdNNNN$$$$$N#S####,#,S,,,,DsD#DDDD9#9999 RX bi8    S      1 1sCx 1 1 1 1F S # c D    0 uS#Xc4i 00 10 uS#Xc4i 00 10000H  *B*B *B *B*B*B*B*B*Br-defence360agent/utils/__pycache__/safe_fileops.cpython-311.pyc0000644000000000000000000004413700000000000021270 0ustar r_jP' @UddlZddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z m Z ddlmZddlmZmZmZddlmZejZejejzejzZejeZeZ ee e!d<dej"fd Z#d)d Z$ej%e$d Z&Gd de'Z(de)d dfdZ*dZ+d*dZ,de)fdZ-de)de)fdZ.de)de)fdZ/de)fdZ0e,ej1Z1e,ej2Z2e d+dZ3e dZ4e de)fdZ5e d,dee)e6ffd Z7e de)d!e6d"e6d#e8fd$Z9d%eee)e6fee6dffd&eee)e6fee6dfffd'Z: d-d%e)d&e)fd(Z;dS).N)ProcessPoolExecutor)contextmanagersuppress)chain)SetTupleUnion)utils _active_poolsloopcKtd}t| |j|g|Rd{V |dt|S#t|wxYw# |dt|w#t|wxYwxYw)N) max_workersF)wait)rr addrun_in_executorshutdowndiscard)r argspools W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_fileops.py_run_in_fresh_executorrs 1 - - -Dd()T)$66666666666 ( MMuM % % %  ! !$ ' ' ' 'M ! !$ ' ' ' ' ( MMuM % % %  ! !$ ' ' ' 'M ! !$ ' ' ' 's/BA44BC%C+C%C""C%returncttD]L} |dd#t$r%}td|Yd}~Ed}~wwxYwtdS)zShutdown all tracked ProcessPoolExecutors. Should be called during agent shutdown to ensure clean process termination. FT)rcancel_futuresz+Error shutting down ProcessPoolExecutor: %sN)listr r Exceptionloggerwarningclear)res rshutdown_process_poolsr")s ]##MM M MMuTM : : : : M M M NNH! L L L L L L L L Ms0 AAActjgtj|tj|||SN)os setgroupssetgidsetuid)funuidgidrs rdropr,:s8LIcNNNIcNNN 3:ceZdZdS)UnsafeFileOperationN)__name__ __module__ __qualname__r-rr/r/AsDr-r/pathctj|}tj|jsYt d|tj|jtj|td|dS)zVerify path is a regular file; remove and raise FileNotFoundError if not. Uses os.lstat() to avoid following symlinks. If the file is a FIFO, symlink, socket, device, etc., it is deleted so the caller can recreate it as a regular file. z:Identity file %s is not a regular file (mode=%s), removingz#Removed non-regular identity file: N) r%lstatstatS_ISREGst_moderrfilemodeunlinkFileNotFoundError)r4sts rensure_regular_filer>Es $B < # #N H  M"* % %   $ Ld L LMMMNNr-ctjt|}|jt jkrt dt|zdS)Nz The file belongs to admin user: T)r%r7strst_uidr get_min_uidr/)filer=s rcheck_non_admin_filerDWsT T  B y5$&&&&! .T :    4r-Fcfd}|S)NcPtjddfd }|S)N)r cBKtj|s std|zt j|}t t|j|g} rt|j}|D]I}tj t|}|j dkr|j dkr|j |j }}n Jtdt|z|ptj}t!|t" |||g|Rd{VS)NzNo such file or directory: rz"Unsafe file operation under root: )r%r4existsr<pathlibPathrreversedparentsr7r@rAst_gidr/asyncioget_event_looprr,) filenamer rr4pathspr=r*r+r) missing_oks rwrapperz$safe.._safe..wrapperbsQ7>>(++ J '1H<<))D(4<004&99E / ..  WSVV__9>>bi1nn!y")CE)83t99D37133D/  r-) functoolswraps)r)rTrSs` r_safezsafe.._safeasK   04          >r-r3)rSrWs` rsaferX`s$!!!!!F Lr-rPcRtj|dSr$)rIrJtouchrPs r_touchr\s$ L  """""r-datacTtj||dSr$)rIrJ write_textrPr]s r _write_textras& L%%d+++++r-cbKtdt||d{VSNT)rS)rXrar`s rr_r_s@3&&&&{33HdCC C C C C C CCr-c`Ktdt|d{VSrc)rXr\r[s rrZrZs>.&&&&v..x88 8 8 8 8 8 88r-Tc#LKd|vrtdt||5}tj|}t j|}tjd|}t|}||ks|j |j krtd||rEtj |j tj |jvrtd|d|VddddS#1swxYwYdS)Nwz'w' mode is not permittedz/proc/self/fd/zUnable to safely read z. File is not in user homedir)r/openr%fstatfilenopwdgetpwnamreadlinkr@rApw_uidrIrJpw_dirrL) rPmodeuserrespect_homedirfr=passwd real_path filename_strs rsafe_open_filervsz d{{!"=>>> h   Xahhjj ! !d##K = = =>> 8}} I % %29 +E+E%&M|&M&MNN N   V]++< --566&.... -sC&DD Dc/BKtj|i|} |Vtt5tj|ddddS#1swxYwYdS#tt5tj|dddw#1swxYwYwxYw)z Context manager which wraps os.open and close file descriptor at the end :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open N)r%rgrOSErrorclose)rkwargsfds ropen_fdr|s! $ !& ! !B g     HRLLL                  Xg     HRLLL                s@AAAAB1B BB BB Bnamec/Kt|g|Rdtji|5}tjd|}||krt d|VddddS#1swxYwYdS)a  Context manager to get a directory file descriptor It also checks if a directory doesn't contain a symlink in the path :param name: full directory name :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open flagsz/proc/self/fd/{}z%Operations on symlinks are prohibitedN)r|r% O_DIRECTORYrlformatr/)r}rrzdir_fdreals r opendir_fdrs  = = = =BN =f = ={-44V<<== 4<<%&MNN N sAA--A14A1rrc #Kd}t|trtt5t j||}t j||jt jzt j z|dddn #1swxYwYt j |||}t||5}|pt j||_ |V|rGtt5t j||jdddn #1swxYwYnO#|rHtt5t j||jdddw#1swxYwYwwxYwddddS#1swxYwYdS)a Context manager to open file object from file name or from file descriptor File object extended with 'st' attribute that contains os.stat_result of the opened file :param f: file name or file descriptor to open :param dir_fd: directory descriptor, ignored if 'f' is a file descriptor :param flags: flags for os.open, ignored if 'f' is a file descriptor :param mode: mode for built-in open Nr)ror)rrro) isinstancer@rrxr%r7chmodr9S_IRUSRS_IWUSRrgr=)rrrrror=fos r open_fobjrs B!S 3 g    6***B H T\1DL@                    GAU6 2 2 2 ad   1r bgajj 1HHH 1g&&11HQRZ0000111111111111111 1g&&11HQRZ00001111111111111111 1 111111111111111111sA BB B :FD%F1D FD F D !F%E1<E$  E1$E( (E1+E( ,E11FFFrris_safec#K|r3t|||5}|dfVddddS#1swxYwYdS||fVdS)z If is_safe flag is True, open file descriptor using name and dir_fd If is_safe is False, return name and dir_fd as is )rrN)r|)r}rrrr{s r safe_tuplers  T& 6 6 6 "d(NNN                  Fls +//srcdstc\|\}}|\}}t|rdn tjz} t||td5} t||| d5} |r|dt j| | t|tr2tj | | j j dddn #1swxYwY|r=t|tr(|r|dtj ||ddddS#1swxYwYdS)Nrrb)rrrowbrrr)W_FLAGSr%O_EXCLrR_FLAGSshutil copyfileobjrr@rrir=r9r;) rr src_unlink dst_overwriteracecallsrc_f src_dir_fddst_f dst_dir_fdw_flagssrc_fodst_fos r_mover sE:E:m:;G  jd   0   *G$   B    vv . . .%%% Bvy/@AAAA B B B B B B B B B B B B B B B  0*UC00 0  IeJ / / / /%000000000000000000s7D!A/C > D! C D!C AD!!D%(D%czKtj|\}}tj|\} } t|5} t| 5} t || t |5} t | | t |5}tj|| }tj }t|tt|j |j| |||| d{V|r*|r(|r|dtj|| |r>tj| |j |j| tj| |j| dddn #1swxYwYdddn #1swxYwYdddn #1swxYwYddddS#1swxYwYdS)Nrr)r%r4splitrrrrr7rNrOrr,rrArMr;chownrr9)rrsafe_srcsafe_dstrrrsrc_dirsrc_namedst_dirdst_namerr src_tuple dst_tuplesrc_str s r safe_mover.s c**GX c**GX G  B J--B Z*gxB J*gx B *555%''$    M M          3( 3  Ihz 2 2 2 2  B HXv}fmJ O O O O HXv~j A A A A=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBsF0&F>FCE*  F*E. .F1E. 2F5 FF FF F F0F F0F F00F47F4)rN)F)T)NrN)FFTFN)rDrXr\rar_rZrr;rvr|rintrboolrrrr3r-rrs   222222////////$$$$$$$$$$!!!!!! + *rz !BK /  8 $ $ +.#%% s&'/// (w'@ ( ( ( (    &'''     )   NcNdNNNN$$$$$N#S####,#,S,,,,DsD#DDDD9#9999 RX bi8    S      1 1sCx 1 1 1 1F S # c D    0 uS#Xc4i 00 10 uS#Xc4i 00 10000H  *B*B *B *B*B*B*B*B*Br-defence360agent/utils/__pycache__/safe_sequence.cpython-311.opt-1.pyc0000644000000000000000000000146200000000000022370 0ustar r_jkddlZdefdZdS)Npcz |n$#t$rtj|cYSwxYw|S)z Make safe sequence from path-like string Useful if p contains unprintable sequence If p is safe to be printed (e.g. via logger) return it as is If it can cause an exception, return bytes instead )encodeUnicodeEncodeErrorosfsencode)rs X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_sequence.pypathr sL  {1~~ Hs 88)rstrr r rs3  C      r defence360agent/utils/__pycache__/safe_sequence.cpython-311.pyc0000644000000000000000000000146200000000000021431 0ustar r_jkddlZdefdZdS)Npcz |n$#t$rtj|cYSwxYw|S)z Make safe sequence from path-like string Useful if p contains unprintable sequence If p is safe to be printed (e.g. via logger) return it as is If it can cause an exception, return bytes instead )encodeUnicodeEncodeErrorosfsencode)rs X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/safe_sequence.pypathr sL  {1~~ Hs 88)rstrr r rs3  C      r defence360agent/utils/__pycache__/serialization.cpython-311.opt-1.pyc0000644000000000000000000001306300000000000022437 0ustar r_jj dZddlZddlZddlZddlZddlZddlmZddlm Z m Z m Z ej e Zde de fdZdZd ed efd Zdd d ed e e effdZdS)zLJSON persistence helpers for small agent state files (no pickle at runtime).N)iscoroutinefunction)AnyCallableUnionobjreturnct|tjr d|DSt|trd|DSt|t t fr d|DS|S)Nc,g|]}t|S _to_jsonable.0items X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/serialization.py z _to_jsonable.. 333t T""333c4i|]\}}|t|Sr r )rkvs r z _to_jsonable..s$;;;tq!<??;;;rc,g|]}t|Sr r rs rrz _to_jsonable..rr) isinstance collectionsdequedictitemslisttuple)rs rr r s#{())433s3333#t<;;syy{{;;;;#e}%%433s3333 Jrctjt|}d|}t |dd5}||dddn #1swxYwYt j||dS)z-Atomically write ``obj`` to ``path`` as JSON.z{}.tmpwutf-8encodingN)jsondumpsr formatopenwriteosreplace)pathrpayloadtmpr"s r_dumpr0sjc**++G //$  C c3 ) ) )Q JsDs A++A/2A/r-attrcfd}|S)zZDecorator: after the wrapped method runs, persist ``self.`` to ``path`` as JSON.ctjfd}tjfd}tr|S|S)Nc|g|Ri|}t|}td|t||SNzWrite %r to %rgetattrloggerdebugr0selfargskwargsresultrr1fr-s rwrapperz2serialize_attr..decorator..wrapper&s]Qt-d---f--F$%%C LL)3 5 5 5 $   MrcK|g|Ri|d{V}t|}td|t||Sr5r6r:s r async_wrapperz8serialize_attr..decorator..async_wrapper.ss1T3D333F33333333F$%%C LL)3 5 5 5 $   Mr) functoolswrapsr)r?r@rBr1r-s` r decoratorz!serialize_attr..decorator%s                         q ! ! ! rr )r-r1rEs`` rserialize_attrrF!s** r)fallbackrGc t|dd5}tj|}dddn #1swxYwYt|trt j|S|S#t$rt d|Yn1t$r%}t d|Yd}~nd}~wwxYwt|r |n|S)zRestore an object from ``path`` (JSON); a top-level list becomes a deque to match the legacy queue API, and missing/unparseable input returns ``fallback`` (called if callable).rr#r$NzCan't find %s to unserializez.Unserialize failed with %r. Returning fallback) r)r&loadrrrrFileNotFoundErrorr8warning Exceptionerrorcallable)r-rGrIres r unserializerQ=s:  $g . . . !)A,,C                c4  *$S)) )  ===5t<<<<< JJJ EqIIIIIIIIJ "(++ 988:::9s8A+5 A+9A+9A++%C CB;;C)__doc__rrCr&loggingr+asynciortypingrrr getLogger__name__r8r r0strrFobjectrQr rrrZsRR  ''''''''''''''''  8 $ $ccCs8CG::::h.>(?::::::rdefence360agent/utils/__pycache__/serialization.cpython-311.pyc0000644000000000000000000001306300000000000021500 0ustar r_jj dZddlZddlZddlZddlZddlZddlmZddlm Z m Z m Z ej e Zde de fdZdZd ed efd Zdd d ed e e effdZdS)zLJSON persistence helpers for small agent state files (no pickle at runtime).N)iscoroutinefunction)AnyCallableUnionobjreturnct|tjr d|DSt|trd|DSt|t t fr d|DS|S)Nc,g|]}t|S _to_jsonable.0items X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/serialization.py z _to_jsonable.. 333t T""333c4i|]\}}|t|Sr r )rkvs r z _to_jsonable..s$;;;tq!<??;;;rc,g|]}t|Sr r rs rrz _to_jsonable..rr) isinstance collectionsdequedictitemslisttuple)rs rr r s#{())433s3333#t<;;syy{{;;;;#e}%%433s3333 Jrctjt|}d|}t |dd5}||dddn #1swxYwYt j||dS)z-Atomically write ``obj`` to ``path`` as JSON.z{}.tmpwutf-8encodingN)jsondumpsr formatopenwriteosreplace)pathrpayloadtmpr"s r_dumpr0sjc**++G //$  C c3 ) ) )Q JsDs A++A/2A/r-attrcfd}|S)zZDecorator: after the wrapped method runs, persist ``self.`` to ``path`` as JSON.ctjfd}tjfd}tr|S|S)Nc|g|Ri|}t|}td|t||SNzWrite %r to %rgetattrloggerdebugr0selfargskwargsresultrr1fr-s rwrapperz2serialize_attr..decorator..wrapper&s]Qt-d---f--F$%%C LL)3 5 5 5 $   MrcK|g|Ri|d{V}t|}td|t||Sr5r6r:s r async_wrapperz8serialize_attr..decorator..async_wrapper.ss1T3D333F33333333F$%%C LL)3 5 5 5 $   Mr) functoolswrapsr)r?r@rBr1r-s` r decoratorz!serialize_attr..decorator%s                         q ! ! ! rr )r-r1rEs`` rserialize_attrrF!s** r)fallbackrGc t|dd5}tj|}dddn #1swxYwYt|trt j|S|S#t$rt d|Yn1t$r%}t d|Yd}~nd}~wwxYwt|r |n|S)zRestore an object from ``path`` (JSON); a top-level list becomes a deque to match the legacy queue API, and missing/unparseable input returns ``fallback`` (called if callable).rr#r$NzCan't find %s to unserializez.Unserialize failed with %r. Returning fallback) r)r&loadrrrrFileNotFoundErrorr8warning Exceptionerrorcallable)r-rGrIres r unserializerQ=s:  $g . . . !)A,,C                c4  *$S)) )  ===5t<<<<< JJJ EqIIIIIIIIJ "(++ 988:::9s8A+5 A+9A+9A++%C CB;;C)__doc__rrCr&loggingr+asynciortypingrrr getLogger__name__r8r r0strrFobjectrQr rrrZsRR  ''''''''''''''''  8 $ $ccCs8CG::::h.>(?::::::rdefence360agent/utils/__pycache__/sshutil.cpython-311.opt-1.pyc0000644000000000000000000005177500000000000021271 0ustar r_jF;ddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl mZddlmZmZddlmZe eZdZdZed Zed Zejd Zd ed efdZdZdZdZ dZ!d#dZ"d e#fdZ$d$ddd efdZ%ejdZ&d e'fdZ(dede'd efdZ)d efdZ*dZ+d%d!Z,d%d e'fd"Z-dS)&N) getLogger)URLError)Path)BACKUP_EXTENSIONatomic_rewrite)open_dir_no_symlinkszFhttps://repo.imunify360.cloudlinux.com/defense360/assisted-cleanup.pubz!clsupport@sshbox\.cloudlinux\.comz/etc/ssh/sshd_configz/etc/ssh/sshd_config.dz^[a-z_][a-z0-9_-]{0,31}\Zusernamereturnct|trt|st d||dkrt dS t j|j}n%#t$r}t d||d}~wwxYw|rtj |st d|d|t tj |dd S) zMHome dir via pwd.getpwnam, not /home/ concatenation, to block path traversal.zinvalid username: rootz/root/.ssh/authorized_keyszno such user: Nz non-absolute home directory for : .sshauthorized_keys) isinstancestr _USERNAME_REmatch ValueErrorrpwdgetpwnampw_dirKeyErrorospathisabsjoin)r homees R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/sshutil.py_resolve_authorized_keysr s h $ $?L,>,>x,H,H?j88=>>>60111B|H%%, BBBjxx9::AB  rw}}T** j8@$$ G     T6+<== > >>sA22 B<BB%IMUNIFY_ASSISTED_CLEANUP_KEY_TTL_DAYSz restrict,ptyc6Kd} tg}tr:|t tdt |D]} |D]}| }| dr]| dsH t| d}|cc|cS#ttf$rYwxYw#t$r*}t d|d|Yd}~d}~wwxYwn4#t$$r'}t d |Yd}~nd}~wwxYw|S#|ccYSxYw) z Detect SSH port from config and its overrides. Searches configs in reverse order to find the last override first. z*.confPort #zFailed to read r NzFailed to get SSH port: )SSH_CONFIG_PATHSSH_CONFIG_DIRexistsextendsortedglobreversed read_text splitlinesstrip startswithintsplit IndexErrorrIOErrorloggerwarning Exception)port config_files config_fileliners r get_ssh_portr>9s D'(  " " G   ~':':8'D'D E E F F F$L11  K '1133>>@@ % %D::<> H6 H11H6ctjtd} t |}|dkr|Sn#t t f$rYnwxYwtS)zDRead the assisted-cleanup key TTL from env, falling back to default.r)renvirongetKEY_TTL_ENV_VARr3 TypeErrorrDEFAULT_KEY_TTL_DAYS)rawttls r _key_ttl_daysr`~se *.." - -C #hh 77J  z "     s?AAnowzdatetime.datetime | Nonec|p-tjtjj}|tjt z}|dS)N)daysz %Y%m%d%H%M)datetimeratimezoneutc astimezone timedeltar`strftime)rabaseexpirys r_expiry_timestamprls]  >(#''(9(=>>D __  !3!I!I!I IF ??< ( ((zOpenSSH_(\d+)\.(\d+)cK tjddtjjtjjd{V}tj|dd{V\}}n?#t tjf$r&}t d|Yd}~dSd}~wwxYw|pd d d p|pd d d }t |}|s%t d |dd dSt|dt|d}}||fdkS)Nsshz-V)stdoutstderrr@zssh -V probe failed: %sFrmrBrCrDz0ssh -V did not match OpenSSH version pattern: %rr')r!r!)rGcreate_subprocess_exec subprocessPIPErI communicaterRrPr7r8rK_OPENSSH_VERSION_REsearchr3group)procrprqroutputrmajorminors r_sshd_supports_expiry_timers 3  %*%*           '/0@0@0B0BANNNNNNNNN W) *0!444uuuuum # #GH # = =' # fWXf&&  & &v . .E  >tt    uu{{1~~&&EKKNN(;(;5E 5>V ##sA-A22B.B))B.pub_keysupports_expirycz|rtdtd}nt}|d|S)Nz,expiry-time="" )KEY_OPTIONS_BASErlr1)rroptionss rbuild_authorized_key_linersJ#%KK5F5H5HKKK" ) )  ) ))rmc|dkrdS tj|}n,#t$rtd|YdSwxYw|j|jfS)zResolve uid/gid for the target user, or (None, None) when not applicable. Returning ``(None, None)`` for root or unknown users lets ``atomic_rewrite`` skip its chown step and preserve the existing file's ownership. r )NNz>user %r not found; leaving authorized_keys ownership untouched)rrrr7r8pw_uidpw_gid)r pws r_target_uid_gidrs{6z \( # #  L    zz  9bi s%AAcd}|r+ tjdd|d}n#t$rYnwxYwtjdtjtjztjz|}|rT ||tj|||tj|dn##t$rtj |wxYw|S)z@O_NOFOLLOW fd for .ssh under *home_fd*; a symlinked .ssh raises.Fri)modedir_fdTr) rmkdirFileExistsErroropenO_RDONLY O_DIRECTORY O_NOFOLLOWchownfchmod BaseExceptionrN)home_fduidgidcreatecreatedssh_fds r _open_ssh_dirrsG   HV% 8 8 8 8GG    D  W bn$r}4F  3?c*** Ife $ $ $ $    HV      Ms --./B B>r c hK t|}n3#t$r&}td|Yd}~dSd}~wwxYwt jdkrtddS t jt }n5#t$r(}td|Yd}~dSd}~wwxYwd|vsd|vrtddSt|td{V }t!|\}}tj|jj} t)|}n8#t*$r+}td |d |Yd}~dSd}~wwxYw t-|||d } nQ#t*$rD}td|jd |Yd}~t j|dSd}~wwxYw d} t jdtjtjz| } t j| d5} | } dddn #1swxYwYn[#t8$rd} d} YnKt*$r?}|jt:jkrtd|d} d} Yd}~nd}~wwxYwtAj!dtDzdzd| }|}|r|#ds|dz }||dzz }tId|d||| | t j| n#t j| wxYw t j|n#t j|wxYwt%d||&dddd S#tN$r(}td|Yd}~dSd}~wwxYw)Nzinstall_pub_key: %sFrzFunction must be run as rootzFailed to download public key:   z*Downloaded public key spans multiple lines)rCannot open home directory r TrzFailed to prepare directory rrrrXizReplacing symlinked %s.*.*\n?backuprr permissionsrz/Installed assisted-cleanup key for user %s (%s)rr'zFailed to install public key: )(r rr7errorrgeteuidurllibrequesturlopenANALYST_PUB_KEY_URLreadrKr1rrrrrrealpathparentrrRrrNrrrfdopenFileNotFoundErrorerrnoELOOPr8rLsub KEY_PATTERNendswithrrMr4r9)r auth_keys_pathrr guarded_linerrrrrrkeys_fdfexistingstripped new_contents rinstall_pub_keyrs!l 5h??NN    LL. 2 2 255555  :<<1   LL7 8 8 85 &&':;; G     LL>1>> ? ? ?55555  7??dgoo LLE F F F50 "<">">>>>>>>   #8,,S w 5 <== *400GG    LLBtBBqBB C C C55555 6  &wSFFF    O>3HOOAOOuuu^ HW     g  , !" , g) bm3%G7C00,A#$6688,,,,,,,,,,,,,,,)(((!H"'KKK(((w%+--NN#;^LLL!H"'KKKKKK (6K'(2 ' ({';';D'A'A(4'K|d22 %  +!         HW    BHW     =    sA & &q )   t  9a99:::uuuuusGO? AAO?A4O?O?D  %O?0AO?FO? G) G O?GO?G-,N.- H;7%H6N. O?6H;;N.?M?.J20M?J& M?&J**M?-J*.M?2L M? L  5LM?L  A M?*N.?NN.O?.O9O?? P1 P,,P1c  t|}n3#t$r&}td|Yd}~dSd}~wwxYwt |\}}t j|jj} t|}n8#t$r+}t d|d|Yd}~dSd}~wwxYw t|||d}nQ#t$rD}t d|jd|Yd}~t j |dSd}~wwxYw t jdt jt jz| }n`#t$rS}t d |d|Yd}~t j |t j |dSd}~wwxYwt j|d 5} t%jt j| j} | } dddn #1swxYwYt1jt4| sHtd | t j |t j |dSt1jd t4zdzd| } | std|dt=dt>z| d||| |t=d| d|||td| t j |t j |dS#t j |wxYw#t j |wxYw#t@$r(}td|Yd}~dSd}~wwxYw)zRemove analyst public key for the specified user This function removes the analyst's public key that was previously installed using the install_pub_key function. returns: True if key was successfully removed, False otherwise. zremove_pub_key: %sNFrr rzCannot open directory rrz Cannot open rz Analyst public key not found in rrrXzFile z will be empty after removalr)rrrrz-Successfully removed analyst public key from TzFailed to remove public key: )!r rr7rrrrrrrrRr8rrNrrrrstatS_IMODEfstatfilenost_moderrLrzrrMrr1rrr9) r rrrrrrrrrrcontentrs rremove_pub_keyrWsS 5h??NN    LL-q 1 1 155555 #8,,Sw 5 <== *400GG    NNDDDDD E E E55555 B  &wSGGG   I^-BIIaIIuuuv HW       8 !! g) bm3%GG !!!NN#G.#G#GA#G#GHHH 555^   HW     g!Yw,,'"&,rx /C/C/K"L"LKffhhG'''''''''''''''yg66!KKK>KK!L   HW     K!fK'(2B #((**KKLLLL%(88  +!% !  )&))   HW         HW      8Q88999uuuuus N" A>N"A>N"BN" C C<N"CN" C N D.*%D)N N")D..N 3.E"!M0" F?, F: M0N $N":F??M0AH5) M05H99M0<H9=:M08N N""B#M0N N"0NN NN"" O,OO)r$)N)r ).rGrdrrrLrurllib.requestrrloggingr urllib.errorrpathlibrdefence360agent.utilsrrdefence360agent.utils.fd_opsr__name__r7rrr(r)compilerrr r]r[rr>rVr3r`rlryboolrrrrrrrmrrsp  !!!!!!BBBBBBBB====== 8  M3 $-...//rz677 ?s?t????,9!   FD  s     ))5)))))!bj!899$$$$$$6*s****** c    (2oooodZZtZZZZZZrmdefence360agent/utils/__pycache__/sshutil.cpython-311.pyc0000644000000000000000000005177500000000000020332 0ustar r_jF;ddlZddlZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z ddl mZddlmZmZddlmZe eZdZdZed Zed Zejd Zd ed efdZdZdZdZ dZ!d#dZ"d e#fdZ$d$ddd efdZ%ejdZ&d e'fdZ(dede'd efdZ)d efdZ*dZ+d%d!Z,d%d e'fd"Z-dS)&N) getLogger)URLError)Path)BACKUP_EXTENSIONatomic_rewrite)open_dir_no_symlinkszFhttps://repo.imunify360.cloudlinux.com/defense360/assisted-cleanup.pubz!clsupport@sshbox\.cloudlinux\.comz/etc/ssh/sshd_configz/etc/ssh/sshd_config.dz^[a-z_][a-z0-9_-]{0,31}\Zusernamereturnct|trt|st d||dkrt dS t j|j}n%#t$r}t d||d}~wwxYw|rtj |st d|d|t tj |dd S) zMHome dir via pwd.getpwnam, not /home/ concatenation, to block path traversal.zinvalid username: rootz/root/.ssh/authorized_keyszno such user: Nz non-absolute home directory for : .sshauthorized_keys) isinstancestr _USERNAME_REmatch ValueErrorrpwdgetpwnampw_dirKeyErrorospathisabsjoin)r homees R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/sshutil.py_resolve_authorized_keysr s h $ $?L,>,>x,H,H?j88=>>>60111B|H%%, BBBjxx9::AB  rw}}T** j8@$$ G     T6+<== > >>sA22 B<BB%IMUNIFY_ASSISTED_CLEANUP_KEY_TTL_DAYSz restrict,ptyc6Kd} tg}tr:|t tdt |D]} |D]}| }| dr]| dsH t| d}|cc|cS#ttf$rYwxYw#t$r*}t d|d|Yd}~d}~wwxYwn4#t$$r'}t d |Yd}~nd}~wwxYw|S#|ccYSxYw) z Detect SSH port from config and its overrides. Searches configs in reverse order to find the last override first. z*.confPort #zFailed to read r NzFailed to get SSH port: )SSH_CONFIG_PATHSSH_CONFIG_DIRexistsextendsortedglobreversed read_text splitlinesstrip startswithintsplit IndexErrorrIOErrorloggerwarning Exception)port config_files config_fileliners r get_ssh_portr>9s D'(  " " G   ~':':8'D'D E E F F F$L11  K '1133>>@@ % %D::<> H6 H11H6ctjtd} t |}|dkr|Sn#t t f$rYnwxYwtS)zDRead the assisted-cleanup key TTL from env, falling back to default.r)renvirongetKEY_TTL_ENV_VARr3 TypeErrorrDEFAULT_KEY_TTL_DAYS)rawttls r _key_ttl_daysr`~se *.." - -C #hh 77J  z "     s?AAnowzdatetime.datetime | Nonec|p-tjtjj}|tjt z}|dS)N)daysz %Y%m%d%H%M)datetimeratimezoneutc astimezone timedeltar`strftime)rabaseexpirys r_expiry_timestamprls]  >(#''(9(=>>D __  !3!I!I!I IF ??< ( ((zOpenSSH_(\d+)\.(\d+)cK tjddtjjtjjd{V}tj|dd{V\}}n?#t tjf$r&}t d|Yd}~dSd}~wwxYw|pd d d p|pd d d }t |}|s%t d |dd dSt|dt|d}}||fdkS)Nsshz-V)stdoutstderrr@zssh -V probe failed: %sFrmrBrCrDz0ssh -V did not match OpenSSH version pattern: %rr')r!r!)rGcreate_subprocess_exec subprocessPIPErI communicaterRrPr7r8rK_OPENSSH_VERSION_REsearchr3group)procrprqroutputrmajorminors r_sshd_supports_expiry_timers 3  %*%*           '/0@0@0B0BANNNNNNNNN W) *0!444uuuuum # #GH # = =' # fWXf&&  & &v . .E  >tt    uu{{1~~&&EKKNN(;(;5E 5>V ##sA-A22B.B))B.pub_keysupports_expirycz|rtdtd}nt}|d|S)Nz,expiry-time="" )KEY_OPTIONS_BASErlr1)rroptionss rbuild_authorized_key_linersJ#%KK5F5H5HKKK" ) )  ) ))rmc|dkrdS tj|}n,#t$rtd|YdSwxYw|j|jfS)zResolve uid/gid for the target user, or (None, None) when not applicable. Returning ``(None, None)`` for root or unknown users lets ``atomic_rewrite`` skip its chown step and preserve the existing file's ownership. r )NNz>user %r not found; leaving authorized_keys ownership untouched)rrrr7r8pw_uidpw_gid)r pws r_target_uid_gidrs{6z \( # #  L    zz  9bi s%AAcd}|r+ tjdd|d}n#t$rYnwxYwtjdtjtjztjz|}|rT ||tj|||tj|dn##t$rtj |wxYw|S)z@O_NOFOLLOW fd for .ssh under *home_fd*; a symlinked .ssh raises.Fri)modedir_fdTr) rmkdirFileExistsErroropenO_RDONLY O_DIRECTORY O_NOFOLLOWchownfchmod BaseExceptionrN)home_fduidgidcreatecreatedssh_fds r _open_ssh_dirrsG   HV% 8 8 8 8GG    D  W bn$r}4F  3?c*** Ife $ $ $ $    HV      Ms --./B B>r c hK t|}n3#t$r&}td|Yd}~dSd}~wwxYwt jdkrtddS t jt }n5#t$r(}td|Yd}~dSd}~wwxYwd|vsd|vrtddSt|td{V }t!|\}}tj|jj} t)|}n8#t*$r+}td |d |Yd}~dSd}~wwxYw t-|||d } nQ#t*$rD}td|jd |Yd}~t j|dSd}~wwxYw d} t jdtjtjz| } t j| d5} | } dddn #1swxYwYn[#t8$rd} d} YnKt*$r?}|jt:jkrtd|d} d} Yd}~nd}~wwxYwtAj!dtDzdzd| }|}|r|#ds|dz }||dzz }tId|d||| | t j| n#t j| wxYw t j|n#t j|wxYwt%d||&dddd S#tN$r(}td|Yd}~dSd}~wwxYw)Nzinstall_pub_key: %sFrzFunction must be run as rootzFailed to download public key:   z*Downloaded public key spans multiple lines)rCannot open home directory r TrzFailed to prepare directory rrrrXizReplacing symlinked %s.*.*\n?backuprr permissionsrz/Installed assisted-cleanup key for user %s (%s)rr'zFailed to install public key: )(r rr7errorrgeteuidurllibrequesturlopenANALYST_PUB_KEY_URLreadrKr1rrrrrrealpathparentrrRrrNrrrfdopenFileNotFoundErrorerrnoELOOPr8rLsub KEY_PATTERNendswithrrMr4r9)r auth_keys_pathrr guarded_linerrrrrrkeys_fdfexistingstripped new_contents rinstall_pub_keyrs!l 5h??NN    LL. 2 2 255555  :<<1   LL7 8 8 85 &&':;; G     LL>1>> ? ? ?55555  7??dgoo LLE F F F50 "<">">>>>>>>   #8,,S w 5 <== *400GG    LLBtBBqBB C C C55555 6  &wSFFF    O>3HOOAOOuuu^ HW     g  , !" , g) bm3%G7C00,A#$6688,,,,,,,,,,,,,,,)(((!H"'KKK(((w%+--NN#;^LLL!H"'KKKKKK (6K'(2 ' ({';';D'A'A(4'K|d22 %  +!         HW    BHW     =    sA & &q )   t  9a99:::uuuuusGO? AAO?A4O?O?D  %O?0AO?FO? G) G O?GO?G-,N.- H;7%H6N. O?6H;;N.?M?.J20M?J& M?&J**M?-J*.M?2L M? L  5LM?L  A M?*N.?NN.O?.O9O?? P1 P,,P1c  t|}n3#t$r&}td|Yd}~dSd}~wwxYwt |\}}t j|jj} t|}n8#t$r+}t d|d|Yd}~dSd}~wwxYw t|||d}nQ#t$rD}t d|jd|Yd}~t j |dSd}~wwxYw t jdt jt jz| }n`#t$rS}t d |d|Yd}~t j |t j |dSd}~wwxYwt j|d 5} t%jt j| j} | } dddn #1swxYwYt1jt4| sHtd | t j |t j |dSt1jd t4zdzd| } | std|dt=dt>z| d||| |t=d| d|||td| t j |t j |dS#t j |wxYw#t j |wxYw#t@$r(}td|Yd}~dSd}~wwxYw)zRemove analyst public key for the specified user This function removes the analyst's public key that was previously installed using the install_pub_key function. returns: True if key was successfully removed, False otherwise. zremove_pub_key: %sNFrr rzCannot open directory rrz Cannot open rz Analyst public key not found in rrrXzFile z will be empty after removalr)rrrrz-Successfully removed analyst public key from TzFailed to remove public key: )!r rr7rrrrrrrrRr8rrNrrrrstatS_IMODEfstatfilenost_moderrLrzrrMrr1rrr9) r rrrrrrrrrrcontentrs rremove_pub_keyrWsS 5h??NN    LL-q 1 1 155555 #8,,Sw 5 <== *400GG    NNDDDDD E E E55555 B  &wSGGG   I^-BIIaIIuuuv HW       8 !! g) bm3%GG !!!NN#G.#G#GA#G#GHHH 555^   HW     g!Yw,,'"&,rx /C/C/K"L"LKffhhG'''''''''''''''yg66!KKK>KK!L   HW     K!fK'(2B #((**KKLLLL%(88  +!% !  )&))   HW         HW      8Q88999uuuuus N" A>N"A>N"BN" C C<N"CN" C N D.*%D)N N")D..N 3.E"!M0" F?, F: M0N $N":F??M0AH5) M05H99M0<H9=:M08N N""B#M0N N"0NN NN"" O,OO)r$)N)r ).rGrdrrrLrurllib.requestrrloggingr urllib.errorrpathlibrdefence360agent.utilsrrdefence360agent.utils.fd_opsr__name__r7rrr(r)compilerrr r]r[rr>rVr3r`rlryboolrrrrrrrmrrsp  !!!!!!BBBBBBBB====== 8  M3 $-...//rz677 ?s?t????,9!   FD  s     ))5)))))!bj!899$$$$$$6*s****** c    (2oooodZZtZZZZZZrmdefence360agent/utils/__pycache__/subprocess.cpython-311.opt-1.pyc0000644000000000000000000000415200000000000021751 0ustar r_j"ZdZddlZddlZddlmZgdZGddejZdZdS)z0General utilities for working with subprocesses.N)PIPE)rCalledProcessError check_outputceZdZdZdZdS)rz'Add stdout,stderr to str representationc&|jrn|jdkrcd|jdtj|j d|jd|jS#t $r!d|j|j |j|jfzcYSwxYwd|j|j|j|jfzS)Nrz Command 'z ' died with z . Stdout: z Stderr: z?Command '%s' died with unknown signal %d. Stdout: %s Stderr: %szDCommand '%s' returned non-zero exit status %d. Stdout: %s Stderr: %s) returncodecmdsignalSignalsstdoutstderr ValueError)selfs U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/subprocess.py__str__zCalledProcessError.__str__ s ? t22 HHHN(KKKKK    /x$/!14; LM +8T_dk4;GH s4A(A32A3N)__name__ __module__ __qualname____doc__rrrr s)11rrc tj|i|S#tj$r,}t|j|j|j|jdd}~wwxYw)z_A wrapper for stdlib subprocess.check_output. Include stdout/stderr in error message. N) subprocessrrrr r r )argskwargses rrr%se &7777  ( L!%18   sA'A  A)rr rr__all__rrrrrrs66  8 8 868     rdefence360agent/utils/__pycache__/subprocess.cpython-311.pyc0000644000000000000000000000415200000000000021012 0ustar r_j"ZdZddlZddlZddlmZgdZGddejZdZdS)z0General utilities for working with subprocesses.N)PIPE)rCalledProcessError check_outputceZdZdZdZdS)rz'Add stdout,stderr to str representationc&|jrn|jdkrcd|jdtj|j d|jd|jS#t $r!d|j|j |j|jfzcYSwxYwd|j|j|j|jfzS)Nrz Command 'z ' died with z . Stdout: z Stderr: z?Command '%s' died with unknown signal %d. Stdout: %s Stderr: %szDCommand '%s' returned non-zero exit status %d. Stdout: %s Stderr: %s) returncodecmdsignalSignalsstdoutstderr ValueError)selfs U/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/subprocess.py__str__zCalledProcessError.__str__ s ? t22 HHHN(KKKKK    /x$/!14; LM +8T_dk4;GH s4A(A32A3N)__name__ __module__ __qualname____doc__rrrr s)11rrc tj|i|S#tj$r,}t|j|j|j|jdd}~wwxYw)z_A wrapper for stdlib subprocess.check_output. Include stdout/stderr in error message. N) subprocessrrrr r r )argskwargses rrr%se &7777  ( L!%18   sA'A  A)rr rr__all__rrrrrrs66  8 8 868     rdefence360agent/utils/__pycache__/support.cpython-311.opt-1.pyc0000644000000000000000000001723100000000000021277 0ustar r_jddlZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZe eZGddeZdZd Zd Zd Zd Zd Z ddZdZdZddddefdZdZdZdS)N)partial) getLogger)Path)ANTIVIRUS_MODEceZdZfdZxZS)ZendeskAPIErrorct||_||_||_t|dS)N)error descriptiondetailssuper__init__)selfr r r __class__s R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/support.pyrzZendeskAPIError.__init__s7 &  %%%%%)__name__ __module__ __qualname__r __classcell__)rs@rrrs8&&&&&&&&&rrz(https://cloudlinux.zendesk.com/api/v2/{}z-https://cloudlinux.zendesk.com/hc/requests/{}iqiiQdlVA,cKt|d{V}t|}||g|d<t||}ttrdnddtddg} |r| t |d|r| t|dt|||| } t| d{VS) z? Send request to support of Imunify360 via Zendesk API N)bodyuploads)nameemail pr_imunify_avpr_im360)idvalueT) requestersubjectcomment custom_fields) _upload_attachmentsdict _PRODUCT_IDr_PRIVACY_POLICY_IDappend _DOCTOR_ID_CLN_ID_post_support_request) sender_emailr!r doctor_keycln attachments upload_tokenr"r r#requests r send_requestr2"s-[99999999L $$$G*^ ,l;;;I (6F__J  "D11 MFJDDEEE <Gc::;;;# G'w// / / / / / //rctjtj||jdS)Nzutf-8)encoding)jsonloadio TextIOWrapperheadersget_content_charset)responses rdecode_as_jsonr<SsB 9  %99'BB     rc tj|}|j}|r|dz }|tj|z }tj|j|j|j|j ||j f}|S)N&) urllibparseurlparsequery urlencode urlunparseschemenetlocpathparamsfragment)rHurlprBs r parse_paramsrL\s c""A GE    V\ # #F + ++E , ! ! 18QVQXuajA  C Jr)rHtimeoutdatac|rt||} tjtj||||5}|jt |fcdddS#1swxYwYdS#tj$rtt$r:}t|ds|j|j t |nifcYd}~Sd}~wwxYw)zHTTP POST *data* to *url* with given *headers*. Add query *params* to the *url* if given. Return (http_status, decoded_json_response) tuple. )rNr9)rMNcode) rLr?r1urlopenRequestrPr<socketrM TimeoutErrorOSErrorhasattrfp)rJrNr9rHrMr;es r _post_datarYhsU(63'' G ^ # # N " "3T7 " C C$   ;=.":"::  ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; > GGGq&!!  vQT-=q)))2FFFFFFF GsBABA8+ B8A<<B?A<BC$/CCCcKtd}ddi}tjt |dd}t j}|dt|||d{V\}}|d krs| d }|r t|d Sd | vrdStd dd|t| dd| d| di)zReturn url of the support request or None if request is suspended, because of we not able to obtain the id of the ticket if it suspended. z requests.json Content-Typezapplication/json)r1T) sort_keysasciiNr1rsuspended_ticketzResponse errorz UNKNOWN ERRORz{!r}r r r ) _API_URL_TMPLformatr5dumpsr%encodeasyncioget_event_looprun_in_executorrYget _HC_URL_TMPLkeysr)r1rJr9rNloopstatusresult request_datas rr+r+sb    / /C12G :d7+++t < < < C CG L LD  ! # #D// j#tWNFF}}zz),,  &&|D'9:: : 6;;== 0 04! /6==3H3H  JJw 0 0 JJ} % % JJy" % %   rc Kd}||Stj}|D]}t|}d|ji}|||d<|dt t td| ddi|d{V\}}|dkr#t d||d ||d d}|S) Nfilenametokenz uploads.jsonr[zapplication/binary)rNr9rHr^z'Failed to upload file %s to Zendesk: %sr upload) rdrerrrfrrYr`ra read_bytesloggerwarning)r/r0rj attachmentrGrHrkrls rr$r$s$L  ! # #D!55 Jdi(  #*F7O#33  $$^44__&&')=>            S== NN9w      !(+G4L r)NNN) rdr7r5rS urllib.parser?urllib.request functoolsrloggingrpathlibr defence360agent.contracts.configrrrs Exceptionrr`rhr&r)r*r'r2r<rLbytesrYr+r$rrrs~ ;;;;;; 8  &&&&&i&&&; >   #  .0.0.0.0b   59$GGG%GGGG2   :!!!!!rdefence360agent/utils/__pycache__/support.cpython-311.pyc0000644000000000000000000001723100000000000020340 0ustar r_jddlZddlZddlZddlZddlZddlZddlmZddl m Z ddl m Z ddl mZe eZGddeZdZd Zd Zd Zd Zd Z ddZdZdZddddefdZdZdZdS)N)partial) getLogger)Path)ANTIVIRUS_MODEceZdZfdZxZS)ZendeskAPIErrorct||_||_||_t|dS)N)error descriptiondetailssuper__init__)selfr r r __class__s R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/support.pyrzZendeskAPIError.__init__s7 &  %%%%%)__name__ __module__ __qualname__r __classcell__)rs@rrrs8&&&&&&&&&rrz(https://cloudlinux.zendesk.com/api/v2/{}z-https://cloudlinux.zendesk.com/hc/requests/{}iqiiQdlVA,cKt|d{V}t|}||g|d<t||}ttrdnddtddg} |r| t |d|r| t|dt|||| } t| d{VS) z? Send request to support of Imunify360 via Zendesk API N)bodyuploads)nameemail pr_imunify_avpr_im360)idvalueT) requestersubjectcomment custom_fields) _upload_attachmentsdict _PRODUCT_IDr_PRIVACY_POLICY_IDappend _DOCTOR_ID_CLN_ID_post_support_request) sender_emailr!r doctor_keycln attachments upload_tokenr"r r#requests r send_requestr2"s-[99999999L $$$G*^ ,l;;;I (6F__J  "D11 MFJDDEEE <Gc::;;;# G'w// / / / / / //rctjtj||jdS)Nzutf-8)encoding)jsonloadio TextIOWrapperheadersget_content_charset)responses rdecode_as_jsonr<SsB 9  %99'BB     rc tj|}|j}|r|dz }|tj|z }tj|j|j|j|j ||j f}|S)N&) urllibparseurlparsequery urlencode urlunparseschemenetlocpathparamsfragment)rHurlprBs r parse_paramsrL\s c""A GE    V\ # #F + ++E , ! ! 18QVQXuajA  C Jr)rHtimeoutdatac|rt||} tjtj||||5}|jt |fcdddS#1swxYwYdS#tj$rtt$r:}t|ds|j|j t |nifcYd}~Sd}~wwxYw)zHTTP POST *data* to *url* with given *headers*. Add query *params* to the *url* if given. Return (http_status, decoded_json_response) tuple. )rNr9)rMNcode) rLr?r1urlopenRequestrPr<socketrM TimeoutErrorOSErrorhasattrfp)rJrNr9rHrMr;es r _post_datarYhsU(63'' G ^ # # N " "3T7 " C C$   ;=.":"::  ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; > GGGq&!!  vQT-=q)))2FFFFFFF GsBABA8+ B8A<<B?A<BC$/CCCcKtd}ddi}tjt |dd}t j}|dt|||d{V\}}|d krs| d }|r t|d Sd | vrdStd dd|t| dd| d| di)zReturn url of the support request or None if request is suspended, because of we not able to obtain the id of the ticket if it suspended. z requests.json Content-Typezapplication/json)r1T) sort_keysasciiNr1rsuspended_ticketzResponse errorz UNKNOWN ERRORz{!r}r r r ) _API_URL_TMPLformatr5dumpsr%encodeasyncioget_event_looprun_in_executorrYget _HC_URL_TMPLkeysr)r1rJr9rNloopstatusresult request_datas rr+r+sb    / /C12G :d7+++t < < < C CG L LD  ! # #D// j#tWNFF}}zz),,  &&|D'9:: : 6;;== 0 04! /6==3H3H  JJw 0 0 JJ} % % JJy" % %   rc Kd}||Stj}|D]}t|}d|ji}|||d<|dt t td| ddi|d{V\}}|dkr#t d||d ||d d}|S) Nfilenametokenz uploads.jsonr[zapplication/binary)rNr9rHr^z'Failed to upload file %s to Zendesk: %sr upload) rdrerrrfrrYr`ra read_bytesloggerwarning)r/r0rj attachmentrGrHrkrls rr$r$s$L  ! # #D!55 Jdi(  #*F7O#33  $$^44__&&')=>            S== NN9w      !(+G4L r)NNN) rdr7r5rS urllib.parser?urllib.request functoolsrloggingrpathlibr defence360agent.contracts.configrrrs Exceptionrr`rhr&r)r*r'r2r<rLbytesrYr+r$rrrs~ ;;;;;; 8  &&&&&i&&&; >   #  .0.0.0.0b   59$GGG%GGGG2   :!!!!!rdefence360agent/utils/__pycache__/threads.cpython-311.opt-1.pyc0000644000000000000000000000302100000000000021205 0ustar r_j0dZddlZddlZddlmZdZdZdS)aHigh-level support for working with threads in asyncio Modified from Python 3.10 stdlib https://github.com/python/cpython/blob/b11a951f16f0603d98de24fee5c023df83ea552c/Lib/asyncio/threads.py (the license GPL-compatible but doesn't require to open-source either). N)events) to_threadcKtj}tj}t j|j|g|Ri|}|d|d{VS)aAsynchronously run function *func* in a separate thread. Any *args and **kwargs supplied for this function are directly passed to *func*. Also, the current :class:`contextvars.Context` is propogated, allowing context variables from the main thread to be accessed in the separate thread. Return a coroutine that can be awaited to get the eventual result of *func* N)rget_running_loop contextvars copy_context functoolspartialrunrun_in_executor)funcargskwargsloopctx func_calls R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/threads.pyrrsp  " $ $D  " $ $C!#'4A$AAA&AAI%%dI66 6 6 6 6 6 66)__doc__r rasyncior__all__rrrrs[  7 7 7 7 7rdefence360agent/utils/__pycache__/threads.cpython-311.pyc0000644000000000000000000000302100000000000020246 0ustar r_j0dZddlZddlZddlmZdZdZdS)aHigh-level support for working with threads in asyncio Modified from Python 3.10 stdlib https://github.com/python/cpython/blob/b11a951f16f0603d98de24fee5c023df83ea552c/Lib/asyncio/threads.py (the license GPL-compatible but doesn't require to open-source either). N)events) to_threadcKtj}tj}t j|j|g|Ri|}|d|d{VS)aAsynchronously run function *func* in a separate thread. Any *args and **kwargs supplied for this function are directly passed to *func*. Also, the current :class:`contextvars.Context` is propogated, allowing context variables from the main thread to be accessed in the separate thread. Return a coroutine that can be awaited to get the eventual result of *func* N)rget_running_loop contextvars copy_context functoolspartialrunrun_in_executor)funcargskwargsloopctx func_calls R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/threads.pyrrsp  " $ $D  " $ $C!#'4A$AAA&AAI%%dI66 6 6 6 6 6 66)__doc__r rasyncior__all__rrrrs[  7 7 7 7 7rdefence360agent/utils/__pycache__/validate.cpython-311.opt-1.pyc0000644000000000000000000001775000000000000021362 0ustar r_jddlmZddlmZmZmZmZmZmZm Z m Z m Z ddl m Z mZmZe dZGddeeZGddeeZd Zdd ZGd d ZdS))Enum) IPV4LENGTH IPV6LENGTHAddressValueError IPv4Address IPv4Network IPv6Address IPv6Network ip_address ip_network)LiteralOptionalUnion)ipv4ipv6ceZdZdZdZdZdS) LocalhostIPz 127.0.0.1z::1c|jSN)valueselfs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/validate.py__str__zLocalhostIP.__str__s zN)__name__ __module__ __qualname__rrrrrrrs- D DrrcXeZdZdZdZdZdZedee dedfdZ dS) NumericIPVersionz=Example: (IPListRecord.version==NumericIPVersion[ip_version])c*t|jSr)strrrs rrzNumericIPVersion.__str__ s4:r ip_versionreturncF|dS|tjkr|jn|jSr)IPV4rr)clsr&s rfrom_ip_versionz NumericIPVersion.from_ip_version#s)  4%..sxxCHz"IP.check_ip_ver..;s'>>c7c>>>>>>>r)anyr*r7)r+r<s `r check_ip_verzIP.check_ip_ver9s/>>>>cfcf-=>>>>>>rcJ t|n#t$rYdSwxYwdSNFT)r ValueErrorr+r2s r is_valid_ipzIP.is_valid_ip=s?  t       55 t   c6|j|i|p |j|i|Sr)r4is_valid_ipv6_network)r+argskwargss ris_valid_ip_networkzIP.is_valid_ip_networkEs>(s(    8 &S & 7 7 7 8rcJ t|n#t$rYdSwxYwdSrA)rrrCs rr1zIP.is_valid_ipv4_addrK?          55 trEcJ t|n#t$rYdSwxYwdSrA)r rrCs ris_valid_ipv6_addrzIP.is_valid_ipv6_addrSrLrEFr2cn t|}n#t$rYdSwxYw|r|jtkSdSrA)rrB prefixlenrr+r2r5ips rr4zIP.is_valid_ipv4_network[T T""BB   55   .<:- -trEcn t|}n#t$rYdSwxYw|r|jtkSdSrA)r rBrPrrQs rrGzIP.is_valid_ipv6_networkirSrEc||r tjS||r tjSt d)NzInvalid ip address)r4r)r*rGr7rBrCs rtype_ofz IP.type_ofwsK  $ $T * * 5L  & &t , , 5L-...r/64cHt||zd}t|S)zConver ipv6 addr to ipv6 network with mask :param str ip: ip for converting :param str mask: ip network mask F)r5)r r%)r+rRmasknetworks rconvert_to_ipv6_networkzIP.convert_to_ipv6_networks&b4i6667||rip_argr'ct|ttfr|St|ttfr7|jdkrt nt}tt||fSt|S)zt Eliminate str from the Union :raise ValueError: if cannot convert ip_arg str to ip network r") isinstancerr rr r<rrr int)r\rPs radopt_to_ipvX_networkzIP.adopt_to_ipvX_networkss f{K8 9 9 8M k : ; ; 8&,n&9&9 zIs6{{I677 7&!!!rnetcpt|jst|jSt|S)zz IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' )r_hostmaskr%network_address)r+ras rip_net_to_stringzIP.ip_net_to_strings2 3<   ,s*++ +3xxrrRct|tr/t|t |S|Sr)r^r r r[r%)r+rRs ripv6_to_64networkzIP.ipv6_to_64networks> b+ & & Es::3r77CCDD D rNF)rW)rrrr*r/__annotations__r7r.r?rDrJr1rNrr%rr r4rGrVr[ staticmethodrr r`rergrrrr)r)5s6B B ??[?[88[8 [[@E  k;67   [ @E  k;67   [ //[/[ "c; [+MN " {K' ( " " "\ "5k)A#Bs[{K45 {K' ([rr)Nrh)enumr ipaddressrrrrrr r r r typingr rrr/r%rr_r!r1r4r)rrrrnsf                      ,+++++++++ N # #t=====sD===&'''2222uuuuuuuuuurdefence360agent/utils/__pycache__/validate.cpython-311.pyc0000644000000000000000000001775000000000000020423 0ustar r_jddlmZddlmZmZmZmZmZmZm Z m Z m Z ddl m Z mZmZe dZGddeeZGddeeZd Zdd ZGd d ZdS))Enum) IPV4LENGTH IPV6LENGTHAddressValueError IPv4Address IPv4Network IPv6Address IPv6Network ip_address ip_network)LiteralOptionalUnion)ipv4ipv6ceZdZdZdZdZdS) LocalhostIPz 127.0.0.1z::1c|jSN)valueselfs S/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/validate.py__str__zLocalhostIP.__str__s zN)__name__ __module__ __qualname__rrrrrrrs- D DrrcXeZdZdZdZdZdZedee dedfdZ dS) NumericIPVersionz=Example: (IPListRecord.version==NumericIPVersion[ip_version])c*t|jSr)strrrs rrzNumericIPVersion.__str__ s4:r ip_versionreturncF|dS|tjkr|jn|jSr)IPV4rr)clsr&s rfrom_ip_versionz NumericIPVersion.from_ip_version#s)  4%..sxxCHz"IP.check_ip_ver..;s'>>c7c>>>>>>>r)anyr*r7)r+r<s `r check_ip_verzIP.check_ip_ver9s/>>>>cfcf-=>>>>>>rcJ t|n#t$rYdSwxYwdSNFT)r ValueErrorr+r2s r is_valid_ipzIP.is_valid_ip=s?  t       55 t   c6|j|i|p |j|i|Sr)r4is_valid_ipv6_network)r+argskwargss ris_valid_ip_networkzIP.is_valid_ip_networkEs>(s(    8 &S & 7 7 7 8rcJ t|n#t$rYdSwxYwdSrA)rrrCs rr1zIP.is_valid_ipv4_addrK?          55 trEcJ t|n#t$rYdSwxYwdSrA)r rrCs ris_valid_ipv6_addrzIP.is_valid_ipv6_addrSrLrEFr2cn t|}n#t$rYdSwxYw|r|jtkSdSrA)rrB prefixlenrr+r2r5ips rr4zIP.is_valid_ipv4_network[T T""BB   55   .<:- -trEcn t|}n#t$rYdSwxYw|r|jtkSdSrA)r rBrPrrQs rrGzIP.is_valid_ipv6_networkirSrEc||r tjS||r tjSt d)NzInvalid ip address)r4r)r*rGr7rBrCs rtype_ofz IP.type_ofwsK  $ $T * * 5L  & &t , , 5L-...r/64cHt||zd}t|S)zConver ipv6 addr to ipv6 network with mask :param str ip: ip for converting :param str mask: ip network mask F)r5)r r%)r+rRmasknetworks rconvert_to_ipv6_networkzIP.convert_to_ipv6_networks&b4i6667||rip_argr'ct|ttfr|St|ttfr7|jdkrt nt}tt||fSt|S)zt Eliminate str from the Union :raise ValueError: if cannot convert ip_arg str to ip network r") isinstancerr rr r<rrr int)r\rPs radopt_to_ipvX_networkzIP.adopt_to_ipvX_networkss f{K8 9 9 8M k : ; ; 8&,n&9&9 zIs6{{I677 7&!!!rnetcpt|jst|jSt|S)zz IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' )r_hostmaskr%network_address)r+ras rip_net_to_stringzIP.ip_net_to_strings2 3<   ,s*++ +3xxrrRct|tr/t|t |S|Sr)r^r r r[r%)r+rRs ripv6_to_64networkzIP.ipv6_to_64networks> b+ & & Es::3r77CCDD D rNF)rW)rrrr*r/__annotations__r7r.r?rDrJr1rNrr%rr r4rGrVr[ staticmethodrr r`rergrrrr)r)5s6B B ??[?[88[8 [[@E  k;67   [ @E  k;67   [ //[/[ "c; [+MN " {K' ( " " "\ "5k)A#Bs[{K45 {K' ([rr)Nrh)enumr ipaddressrrrrrr r r r typingr rrr/r%rr_r!r1r4r)rrrrnsf                      ,+++++++++ N # #t=====sD===&'''2222uuuuuuuuuurdefence360agent/utils/__pycache__/whmcs.cpython-311.opt-1.pyc0000644000000000000000000003043400000000000020704 0ustar r_jhddlZddlZddlZddlmcmcmZddl m Z ddl m Z ddl mZddlmZmZddlmZmZmZe eZeegZGddZd Zd Zd Zd Zd ZdZdZ dZ!dZ"dS)N) getLogger)config) update_config)update_users_protection MyImunify)MU_PLUGIN_INSTALLATIONADVICE_EMAIL_NOTIFICATIONWordPressMuPluginc"eZdZdZdZdZdZdS) WhmcsConfz read/write data passed by whmcs Internal use, for commands called from whcms only it saves ALL data came from whcms w/o any validation deliberately in order to simplify compatability with current installed whmcs plugin z/var/imunify360/whmcs_data.jsonctj|jsiS t|jd5}|}dddn #1swxYwYnA#t $r4}t dt|icYd}~Sd}~wwxYw tj |}n9#tj tf$r t d|icYSwxYw|S)Nrz"Failed to read whmcs data file: %sz"Malformed file with whmcs data: %s) ospathexistsopenreadIOErrorloggererrorstrjsonloadsJSONDecodeError ValueError)selffraw_dataedatas P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/whmcs.pyrzWhmcsConf.read!s=w~~di(( I di%% $6688 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $    LL=s1vv F F FIIIIII  :h''DD$j1    LL=x H H HIII  sRA*A A*A""A*%A"&A** B(4)B#B(#B(,C3C76C7cd|}|| t|jd5}t j||dddddS#1swxYwYdS#t $r3}tdt|Yd}~dSd}~wwxYw)z Saves ALL data passed by WHMCS it should not have any validations deliberately to be as compatible as possible with current installed WHMCS plugin w)indentNz&Failed to write whmcs data to file: %s) rupdaterrrdumprrrr)rr current_datafilers r!savezWhmcsConf.save4s yy{{ D!!! Kdi%% 8 ,Q7777 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 K K K LLA3q66 J J J J J J J J J Ks;A2A% A2%A))A2,A)-A22 B/<(B**B/N)__name__ __module__ __qualname____doc__rrr*r!r r sH -D&KKKKKr0r cpK|tj}t||d{VSN)getrMY_IMUNIFY_KEY mi_update)sinkr my_imunify_updatess r!sync_billing_datar8Es>&"7884!344 4 4 4 4 4 44r0cj|dkr dddd|fS|dkr dddd|fS|dkrd |fS||fS) zf Convert several keys to config key, otherwise just return same key any key is acceptable statusenableTF)activeinactive protection)enableddisabledmu_plugin_installationsmart_advice_allowedr/keyvalues r!convert_to_config_key_valuerFJs  h !        !      ( ( (%u,, :r0cV|dkrd|rdndfS|dkrd|rdndfS|dkrd |fS||fS) zk Convert several keys from config format, otherwise just return same key any key is acceptable r;r:r<r=r>r?r@rBrAr/rCs r!convert_from_config_key_valuerHdsa  he;((<<  5@iijAA & & &'.. :r0c\Ktjd{VSr2)hp HostingPanel get_usersr/r0r!rLrLrs2"",,.. . . . . . ..r0c \K|stddS|d}|r6td|dit ||d{Vt ||t|dstgd{VStd{V|dgp}fd|D}|rZtdt|t||td|ddd{Vntd t|d{VS) z Updates supported parameters if passed, otherwise does nothing updates 2 config parameters (if specified): status and purchase_page_url updates protection status for users (if specified) zNothing to update for MyImunifyNr:r>userscg|]}|v| Sr/r/).0user all_userss r! zmi_update..s*):):):):):r0z'Updating protection status for users=%sz!No users to update protection for)rinfor3r r*update_configsr "prepare_for_mu_plugin_installationrget_current_whmcs_datarLrrrFwarning)r6requested_myimunify_datawhmcs_activation_status target_usersfiltered_passed_usersrRs @r!r5r5vs  $ 56666::8DDM ($<$@$@$J$JKLLL 7 8 88888888:: $$%;<< $ ' ' 5 50+B/////////kk!!!!!!I+//<<I L% < 5 % & &   &  ! '6|D              :;;;'(=>> > > > > > >>r0cKtjrdgnddgtfd|D}td|D}i}|r||tj<|r||d<|r@t dt|t||d{VdSdS)Npurchase_page_urlr:c3FK|]\}}|v t||VdSr2)rF)rPparamrEmi_config_parameterss r! z!update_configs..sI E5 ( ( ( $E511 ( ( ( (r0c3NK|] \}}|tvt||V!dSr2)MU_PLUGIN_KEYSrFrPrarEs r!rcz!update_configs..sG E5 N " " $E511 " " " "r0 CONTROL_PANELzUpdating config with data: %s) ris_mi_freemium_licensedictitemsr4rrUrr)r6rZmi_config_datamu_plugin_data config_dictrbs @r!rVrVs1  ( * * - !8 , 4::<<N 4::<<N K<-; F)*6'5 O$/ 3S5E5EFFFD+...........//r0cK|rUtjtj|n$tj}d|DS)zp Returns information from database based on passed users if no users passed - returns for all users cXg|]'}|dtd|ddd(S)rQr>rT)rQr>rH)rPitems r!rSz"get_users_info..sW     L7d<0     r0)rselectwhererQin_dicts)rNresults r!get_users_inforws  (   !3!3E!:!:;;AACCC     % % ' '      r0cKtj}td|tjiD}|d}td|dd|t<|t|t<t|d{V|d<|S)z Returns the current configuration and user protection status. {MY_IMUNIFY: {'status': 'active/inactive', 'purchase_page_url': 'SOMEURL', 'protection': []}} c3<K|]\}}t||VdSr2rprfs r!rcz)get_current_whmcs_data..sD E5 &eU33r0rgrBrTNr>) r ConfigFileconfig_to_dictrir3r4rjrHrr rw)rN conf_datacurrent_configcp_datas r!rXrXs !##2244I%MM&*?DDJJLLN mmO,,G-J ,B C C... N)*18 !11N,-*8)>)>#>#>#>#>#>#>N< r0c tjjddztjddd||tj dz}|S)N/z/?cloudlinux_advantage provisioningmy_imunify_account_protection)mactionsuiteusernamedomain server_ip) rMyImunifyConfigPURCHASE_PAGE_URLrstripurllibparse urlencoderJrK get_server_ip)rrpurchase_url_links r!get_upgrade_url_linkrsx077<<   , +(8$ _..<<>>      r0)#rr urllib.parser+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrJloggingrdefence360agent.contractsrdefence360agent.utils.configrdefence360agent.myimunify.modelrr)defence360agent.utils.wordpress_mu_pluginrr r r+rrer r8rFrHrLr5rVrwrXrr/r0r!rs 888888888888,,,,,,666666NNNNNNNN 8  (*CD+K+K+K+K+K+K+K+K\555 4   ///-?-?-?`///B*.r0defence360agent/utils/__pycache__/whmcs.cpython-311.pyc0000644000000000000000000003043400000000000017745 0ustar r_jhddlZddlZddlZddlmcmcmZddl m Z ddl m Z ddl mZddlmZmZddlmZmZmZe eZeegZGddZd Zd Zd Zd Zd ZdZdZ dZ!dZ"dS)N) getLogger)config) update_config)update_users_protection MyImunify)MU_PLUGIN_INSTALLATIONADVICE_EMAIL_NOTIFICATIONWordPressMuPluginc"eZdZdZdZdZdZdS) WhmcsConfz read/write data passed by whmcs Internal use, for commands called from whcms only it saves ALL data came from whcms w/o any validation deliberately in order to simplify compatability with current installed whmcs plugin z/var/imunify360/whmcs_data.jsonctj|jsiS t|jd5}|}dddn #1swxYwYnA#t $r4}t dt|icYd}~Sd}~wwxYw tj |}n9#tj tf$r t d|icYSwxYw|S)Nrz"Failed to read whmcs data file: %sz"Malformed file with whmcs data: %s) ospathexistsopenreadIOErrorloggererrorstrjsonloadsJSONDecodeError ValueError)selffraw_dataedatas P/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/whmcs.pyrzWhmcsConf.read!s=w~~di(( I di%% $6688 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $    LL=s1vv F F FIIIIII  :h''DD$j1    LL=x H H HIII  sRA*A A*A""A*%A"&A** B(4)B#B(#B(,C3C76C7cd|}|| t|jd5}t j||dddddS#1swxYwYdS#t $r3}tdt|Yd}~dSd}~wwxYw)z Saves ALL data passed by WHMCS it should not have any validations deliberately to be as compatible as possible with current installed WHMCS plugin w)indentNz&Failed to write whmcs data to file: %s) rupdaterrrdumprrrr)rr current_datafilers r!savezWhmcsConf.save4s yy{{ D!!! Kdi%% 8 ,Q7777 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 K K K LLA3q66 J J J J J J J J J Ks;A2A% A2%A))A2,A)-A22 B/<(B**B/N)__name__ __module__ __qualname____doc__rrr*r!r r sH -D&KKKKKr0r cpK|tj}t||d{VSN)getrMY_IMUNIFY_KEY mi_update)sinkr my_imunify_updatess r!sync_billing_datar8Es>&"7884!344 4 4 4 4 4 44r0cj|dkr dddd|fS|dkr dddd|fS|dkrd |fS||fS) zf Convert several keys to config key, otherwise just return same key any key is acceptable statusenableTF)activeinactive protection)enableddisabledmu_plugin_installationsmart_advice_allowedr/keyvalues r!convert_to_config_key_valuerFJs  h !        !      ( ( (%u,, :r0cV|dkrd|rdndfS|dkrd|rdndfS|dkrd |fS||fS) zk Convert several keys from config format, otherwise just return same key any key is acceptable r;r:r<r=r>r?r@rBrAr/rCs r!convert_from_config_key_valuerHdsa  he;((<<  5@iijAA & & &'.. :r0c\Ktjd{VSr2)hp HostingPanel get_usersr/r0r!rLrLrs2"",,.. . . . . . ..r0c \K|stddS|d}|r6td|dit ||d{Vt ||t|dstgd{VStd{V|dgp}fd|D}|rZtdt|t||td|ddd{Vntd t|d{VS) z Updates supported parameters if passed, otherwise does nothing updates 2 config parameters (if specified): status and purchase_page_url updates protection status for users (if specified) zNothing to update for MyImunifyNr:r>userscg|]}|v| Sr/r/).0user all_userss r! zmi_update..s*):):):):):r0z'Updating protection status for users=%sz!No users to update protection for)rinfor3r r*update_configsr "prepare_for_mu_plugin_installationrget_current_whmcs_datarLrrrFwarning)r6requested_myimunify_datawhmcs_activation_status target_usersfiltered_passed_usersrRs @r!r5r5vs  $ 56666::8DDM ($<$@$@$J$JKLLL 7 8 88888888:: $$%;<< $ ' ' 5 50+B/////////kk!!!!!!I+//<<I L% < 5 % & &   &  ! '6|D              :;;;'(=>> > > > > > >>r0cKtjrdgnddgtfd|D}td|D}i}|r||tj<|r||d<|r@t dt|t||d{VdSdS)Npurchase_page_urlr:c3FK|]\}}|v t||VdSr2)rF)rPparamrEmi_config_parameterss r! z!update_configs..sI E5 ( ( ( $E511 ( ( ( (r0c3NK|] \}}|tvt||V!dSr2)MU_PLUGIN_KEYSrFrPrarEs r!rcz!update_configs..sG E5 N " " $E511 " " " "r0 CONTROL_PANELzUpdating config with data: %s) ris_mi_freemium_licensedictitemsr4rrUrr)r6rZmi_config_datamu_plugin_data config_dictrbs @r!rVrVs1  ( * * - !8 , 4::<<N 4::<<N K<-; F)*6'5 O$/ 3S5E5EFFFD+...........//r0cK|rUtjtj|n$tj}d|DS)zp Returns information from database based on passed users if no users passed - returns for all users cXg|]'}|dtd|ddd(S)rQr>rT)rQr>rH)rPitems r!rSz"get_users_info..sW     L7d<0     r0)rselectwhererQin_dicts)rNresults r!get_users_inforws  (   !3!3E!:!:;;AACCC     % % ' '      r0cKtj}td|tjiD}|d}td|dd|t<|t|t<t|d{V|d<|S)z Returns the current configuration and user protection status. {MY_IMUNIFY: {'status': 'active/inactive', 'purchase_page_url': 'SOMEURL', 'protection': []}} c3<K|]\}}t||VdSr2rprfs r!rcz)get_current_whmcs_data..sD E5 &eU33r0rgrBrTNr>) r ConfigFileconfig_to_dictrir3r4rjrHrr rw)rN conf_datacurrent_configcp_datas r!rXrXs !##2244I%MM&*?DDJJLLN mmO,,G-J ,B C C... N)*18 !11N,-*8)>)>#>#>#>#>#>#>N< r0c tjjddztjddd||tj dz}|S)N/z/?cloudlinux_advantage provisioningmy_imunify_account_protection)mactionsuiteusernamedomain server_ip) rMyImunifyConfigPURCHASE_PAGE_URLrstripurllibparse urlencoderJrK get_server_ip)rrpurchase_url_links r!get_upgrade_url_linkrsx077<<   , +(8$ _..<<>>      r0)#rr urllib.parser+defence360agent.subsys.panels.hosting_panelsubsyspanels hosting_panelrJloggingrdefence360agent.contractsrdefence360agent.utils.configrdefence360agent.myimunify.modelrr)defence360agent.utils.wordpress_mu_pluginrr r r+rrer r8rFrHrLr5rVrwrXrr/r0r!rs 888888888888,,,,,,666666NNNNNNNN 8  (*CD+K+K+K+K+K+K+K+K\555 4   ///-?-?-?`///B*.r0defence360agent/utils/__pycache__/wordpress_mu_plugin.cpython-311.opt-1.pyc0000644000000000000000000000402600000000000023670 0ustar r_jZddlZddlmZeeZdZdZeegZGddZdS)N) getLoggermu_plugin_installationadvice_email_notificationceZdZdZdS)WordPressMuPlugincTt|dk|gs8tdt|t|dS|s*tdt|dStjdstddS)z Must use plugin works only if cl-hosting-smart-advice is installed So it is a requirement to be sure it is installed It is expected to be installed by default with Imunify360 activeznNothing to prepare for Must Use plugin as settings are not turned on, activation status=%s mu_plugin_status=%sNz=Nothing to prepare for Must Use plugin as mu_plugin_status=%sz!/usr/sbin/cl-hosting-smart-advicezccl-hosting-smart-advice rpm package is not installed in the system, please install it and try again)allloggerwarningstrospathexists ValueError)selfactivation_statusmu_plugin_statuss ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/wordpress_mu_plugin.py"prepare_for_mu_plugin_installationz4WordPressMuPlugin.prepare_for_mu_plugin_installation s%13CDEE  NNN%&&$%%     F  NN)$%%    Fw~~ABB A   N)__name__ __module__ __qualname__rrrrr s#rr) rloggingrrr MU_PLUGIN_INSTALLATIONADVICE_EMAIL_NOTIFICATIONMU_PLUGIN_KEYSrrrrr sx  8  17(*CDrdefence360agent/utils/__pycache__/wordpress_mu_plugin.cpython-311.pyc0000644000000000000000000000402600000000000022731 0ustar r_jZddlZddlmZeeZdZdZeegZGddZdS)N) getLoggermu_plugin_installationadvice_email_notificationceZdZdZdS)WordPressMuPlugincTt|dk|gs8tdt|t|dS|s*tdt|dStjdstddS)z Must use plugin works only if cl-hosting-smart-advice is installed So it is a requirement to be sure it is installed It is expected to be installed by default with Imunify360 activeznNothing to prepare for Must Use plugin as settings are not turned on, activation status=%s mu_plugin_status=%sNz=Nothing to prepare for Must Use plugin as mu_plugin_status=%sz!/usr/sbin/cl-hosting-smart-advicezccl-hosting-smart-advice rpm package is not installed in the system, please install it and try again)allloggerwarningstrospathexists ValueError)selfactivation_statusmu_plugin_statuss ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/wordpress_mu_plugin.py"prepare_for_mu_plugin_installationz4WordPressMuPlugin.prepare_for_mu_plugin_installation s%13CDEE  NNN%&&$%%     F  NN)$%%    Fw~~ABB A   N)__name__ __module__ __qualname__rrrrr s#rr) rloggingrrr MU_PLUGIN_INSTALLATIONADVICE_EMAIL_NOTIFICATIONMU_PLUGIN_KEYSrrrrr sx  8  17(*CDrdefence360agent/utils/__pycache__/zipsafe.cpython-311.opt-1.pyc0000644000000000000000000000254300000000000021224 0ustar r_j8ddlZddlmZdejdeddfdZdS)N)Pathzfdestreturnct|}|D]}|drt d|t|j}d|vrt d|||z }||kr||jvrt d|||dS)N)/\z!Unsafe absolute zip member path: z..z)Unsafe parent-traversal zip member path: z Zip member escapes destination: )rresolvenamelist startswith ValueErrorpartsparents extractall)rr dest_resolvedmemberrtargets R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/zipsafe.pysafe_extractallrsJJ&&((M++-- O O   [ ) ) P*ffNOO OV " 5==*AGI  &(1133 ] " "}FN'J'J*VVMNN NMM-     )zipfilepathlibrZipFilerrrrsU ! !t ! ! ! ! ! ! !rdefence360agent/utils/__pycache__/zipsafe.cpython-311.pyc0000644000000000000000000000254300000000000020265 0ustar r_j8ddlZddlmZdejdeddfdZdS)N)Pathzfdestreturnct|}|D]}|drt d|t|j}d|vrt d|||z }||kr||jvrt d|||dS)N)/\z!Unsafe absolute zip member path: z..z)Unsafe parent-traversal zip member path: z Zip member escapes destination: )rresolvenamelist startswith ValueErrorpartsparents extractall)rr dest_resolvedmemberrtargets R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/utils/zipsafe.pysafe_extractallrsJJ&&((M++-- O O   [ ) ) P*ffNOO OV " 5==*AGI  &(1133 ] " "}FN'J'J*VVMNN NMM-     )zipfilepathlibrZipFilerrrrsU ! !t ! ! ! ! ! ! !rdefence360agent/utils/_shutil.py0000644000000000000000000000200400000000000013723 0ustar """High-level file operations.""" import errno import logging import os import shutil logger = logging.getLogger(__name__) def is_safe_subdir_name(name) -> bool: return ( isinstance(name, str) and bool(name) and "\x00" not in name and name == os.path.basename(name) and name not in (".", "..") ) def rmtree(path, ignore_errors=False, onerror=None, *, max_tries=3): """More robust shutil.rmtree. Retry on "Directory not empty" race condition: https://github.com/ansible/ansible/issues/34335#issuecomment-362995700 """ for i in range(1, max_tries + 1): try: return shutil.rmtree(path, ignore_errors, onerror) except OSError as e: if i == max_tries or e.errno not in [ errno.EEXIST, errno.ENOTEMPTY, ]: raise # Got "Directory not empty" and attempts are not exhausted yet logger.warning("Can't remove %s tree, reason: %s", path, e) defence360agent/utils/antivirus_mode.py0000644000000000000000000000076100000000000015314 0ustar import functools import inspect from defence360agent.contracts.config import ANTIVIRUS_MODE def skip(f): @functools.wraps(f) async def async_wrapper(*args, **kwargs): return None if ANTIVIRUS_MODE else await f(*args, **kwargs) @functools.wraps(f) def wrapper(*args, **kwargs): return None if ANTIVIRUS_MODE else f(*args, **kwargs) return async_wrapper if inspect.iscoroutinefunction(f) else wrapper enabled, disabled = ANTIVIRUS_MODE, not ANTIVIRUS_MODE defence360agent/utils/async_utils.py0000644000000000000000000000131600000000000014616 0ustar from typing import List, Union, Tuple import asyncio class AsyncIterate: # not AsyncIterable because python use this name already def __init__(self, data: Union[List, Tuple]): self.queue = iter(data) def __aiter__(self): return self async def __anext__(self): data = await self.fetch_data() if data is not None: return data else: raise StopAsyncIteration async def fetch_data(self): try: item = next(self.queue) except StopIteration: item = None return item async def gather(*tasks: List) -> AsyncIterate: results = await asyncio.gather(*tasks) return AsyncIterate(results) defence360agent/utils/benchmark.py0000644000000000000000000000103200000000000014206 0ustar import time from types import TracebackType class Benchmark: def __enter__(self) -> None: self.start_time = time.monotonic_ns() return self def __exit__( self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None, ) -> None: self.end_time = time.monotonic_ns() self.elapsed_time_ns = self.end_time - self.start_time @property def elapsed_time_ms(self) -> float: return self.elapsed_time_ns * 1e-6 defence360agent/utils/buffer.py0000644000000000000000000000363100000000000013534 0ustar class LineBufferOverflow(Exception): pass class LineBuffer(object): """ Allows to accumulate data, and than iterate over it getting tokens split by line breaks '\n'. If at the end there is no line break, the data will sit in the line buffer until more data with line break comes in. """ MAX_SIZE = 16 * 1024 * 1024 def __init__(self): self.buf = "" def append(self, data): if len(self.buf) + len(data) > self.MAX_SIZE: self.buf = "" raise LineBufferOverflow( "LineBuffer exceeded maximum size of {} bytes".format( self.MAX_SIZE ) ) self.buf += data def __iter__(self): return self def __next__(self): pos = self.buf.find("\n") if pos != -1: result = self.buf[0:pos] self.buf = self.buf[pos + 1 :] return result raise StopIteration def clean(self): self.buf = "" class SizeBufferOverflow(Exception): pass class SizeBuffer: MAX_SIZE = 16 * 1024 * 1024 def __init__(self, size_len=2): self._buf = b"" self._size_len = size_len def append(self, data): if len(self._buf) + len(data) > self.MAX_SIZE: self._buf = b"" raise SizeBufferOverflow( "SizeBuffer exceeded maximum size of {} bytes".format( self.MAX_SIZE ) ) self._buf += data def __iter__(self): return self def __next__(self): if not self._buf: raise StopIteration size = int.from_bytes(self._buf[: self._size_len], "big") if len(self._buf[self._size_len :]) >= size: data = self._buf[self._size_len : self._size_len + size] self._buf = self._buf[self._size_len + size :] return data raise StopIteration defence360agent/utils/check_db.py0000644000000000000000000001733500000000000014013 0ustar import logging import itertools import os from contextlib import suppress from datetime import datetime from shutil import copy from sqlite3 import connect, DatabaseError from playhouse.sqlite_ext import SqliteExtDatabase from defence360agent.application import app from defence360agent import simple_rpc from defence360agent.contracts.config import Model from defence360agent.model import simplification logger = logging.getLogger(__name__) class OperationError(Exception): pass WORKAROUND_MSG = "Blank database will be created on agent start " def check_and_repair(): base = Model.PATH if simple_rpc.is_running(): raise OperationError( "Cannot perform database check and backup while agent is running. " "Please, stop the imunify360 agent with `service imunify360 stop`" ) elif not os.path.isfile(base): raise OperationError( "DB %s is not exists. %s" % (base, WORKAROUND_MSG) ) else: if is_db_corrupted(base): backup = make_backup(base) if not backup: raise OperationError( "Cannot proceed without backup copy of the database." "Please contact imunify360 support team at " "https://cloudlinux.zendesk.com" ) dump = dump_to_sql(base) logger.info("Removing original corrupted database at %s" % base) # TODO: Notify user in UI that original DB was dropped os.remove(base) if not dump: raise OperationError( "Cannot dump database to sql. Old DB backuped at %s. %s" % (backup, WORKAROUND_MSG) ) else: restored = load_from_sql(base, dump) if not restored: raise OperationError( "Loading dump to new database failed. Database will " "be recreated during migrations." ) if is_db_corrupted(restored): os.remove(restored) raise OperationError( "Restored database is still corrupt. Removing " "restored database. %s" % WORKAROUND_MSG ) logger.info( "Database restored successfully. Removing dump %s" % dump ) os.remove(dump) try: logger.info("Performing migrations on restored database") simplification.migrate() except Exception as e: os.remove(base) raise OperationError( "Migrations on restored database failed: %s. %s" % (e, WORKAROUND_MSG) ) else: if not all_tables_are_present(): os.remove(base) raise OperationError( "Restored database does not " "contain all necessary tables. " "%s" % WORKAROUND_MSG ) def mark_with_timestamp(filename, extension=None): """ >>> mark_with_timestamp('/var/imunify360/imunify360.db') '/var/imunify360/imunify360.db_2017-09-26_03:33:44.705967' >>> mark_with_timestamp('/var/imunify360/imunify360.db', extension='sql') '/var/imunify360/imunify360.db_2017-09-26_03:34:01.098544.sql' """ instant = datetime.now() basename = "{}_{}".format(filename, instant.isoformat("_")) if extension: return basename + ".%s" % extension else: return basename def is_db_corrupted(db_path): logger.info("Database %s integrity check..." % db_path) is_corrupted = True with connect(db_path) as connection: try: cursor = connection.execute("PRAGMA INTEGRITY_CHECK;") result = next(cursor) if "ok" in result: logger.info("Database integrity check succeeded.") is_corrupted = False except DatabaseError as e: logger.warning("DatabaseError detected: %s", e) return is_corrupted def dump_to_sql(db_path): dumpfile = mark_with_timestamp(db_path, extension="sql") logger.info("Dumping imunify360 database to %s" % dumpfile) try: with open(dumpfile, "w") as dump, connect(db_path) as connection: for row in connection.iterdump(): dump.write(row) except (DatabaseError, OSError) as e: logger.error("Error during dump: %s. Operation aborted" % e) with suppress(OSError): os.remove(dumpfile) dumpfile = None return dumpfile def load_from_sql(db_path, dumpfile): # This is unlikely to happen because we delete the original file # but to be defensive here won't hurt in case of reuse in other places. if os.path.exists(db_path): logger.warning( "Database already exists. Loading dump to existing " "database may cause errors. Operation aborted" ) return None logger.info( "Reading dump %s into new database %s..." % (dumpfile, db_path) ) with open(dumpfile, "r") as dump, connect(db_path) as connection: # We cannot read line by line because SQL statements are dumped # not in a statement-per-line way try: sql = dump.read() connection.executescript(sql) except MemoryError as e: logger.error(e) with suppress(OSError): os.remove(db_path) db_path = None return db_path def make_backup(db_path): logger.info("Making backup of the %s..." % db_path) backup_filename = mark_with_timestamp(db_path, "backup") try: copy(db_path, backup_filename) logger.info("Database copied successfully to: %s " % backup_filename) except Exception as e: logger.error("Making backup failed: %s", e) with suppress(OSError): os.remove(backup_filename) backup_filename = None return backup_filename def all_tables_are_present(): logger.info( "Verifying that db schema is up-to-date and all tables are present..." ) models = itertools.chain( *[ simplification.get_models(module) for module in app.MODULES_WITH_MODELS ] ) if all(model.table_exists() for model in models): logger.info("All tables are present") return True else: logger.error("Some tables are missing in db.") return False def recreate_schema() -> None: simplification.instance.db.init(Model.PATH) logger.info("Recreating schema for linked DBs...") attached_schemas = [] for db_path, schema in app.MIGRATIONS_ATTACHED_DBS: logger.info("Attach db: %s", db_path) simplification.instance.db.execute_sql( "ATTACH ? AS ?", (db_path, schema) ) attached_schemas.append(schema) recreate_schema_models(simplification.instance.db, attached_schemas) logger.info("Schema recreated successfully.") def recreate_schema_models( db: SqliteExtDatabase, target_schemas: list[str] ) -> None: models_to_create = [ model for model in itertools.chain( *[ simplification.get_models(module) for module in app.MODULES_WITH_MODELS ] ) if model._meta.schema in target_schemas ] logger.info("%r", models_to_create) # bind models to the db to avoid issues related to initialization order db.bind(models_to_create) db.create_tables(models_to_create) logger.info("Schema models recreated successfully.") defence360agent/utils/check_lock.py0000644000000000000000000000153000000000000014344 0ustar import random import time def check_lock(check_lock_period: int, lock_file, jitter: bool = False): if not lock_file.exists(): lock_file.parent.mkdir(parents=True, exist_ok=True) if jitter: delay = random.randrange(int(check_lock_period)) lock_file.write_text(str(time.time() + delay + check_lock_period)) return delay lock_file.write_text(str(time.time() + check_lock_period)) return 0 if (time_left := is_period_passed(check_lock_period, lock_file)) <= 0: lock_file.write_text(str(time.time() + check_lock_period)) return 0 else: return time_left def is_period_passed(period, lock_file): try: when_to_run = float(lock_file.read_text()) except (FileNotFoundError, ValueError): return 0 return when_to_run - time.time() defence360agent/utils/cli.py0000644000000000000000000002207100000000000013031 0ustar from collections import defaultdict import json import os import subprocess import sys import time import yaml PRETTY_JSON_ARGS = {"sort_keys": True, "indent": 2, "separators": (",", ": ")} EXITCODE_NOT_FOUND = 2 EXITCODE_WARNING = 3 EXITCODE_GENERAL_ERROR = 11 PAGERS = ["/bin/less", "/bin/more"] SUCCESS, WARNING, ERROR = "success", "warnings", "error" # see simple_rpc _CLI_MSG_PREFIX = {WARNING: "WARNING", ERROR: "ERROR"} EXIT_CODES = { SUCCESS: 0, WARNING: EXITCODE_WARNING, ERROR: EXITCODE_GENERAL_ERROR, } def pager(data): pager = os.environ.get( "PAGER", next((p for p in PAGERS if os.path.isfile(p)), None) ) if pager is None: print(data) else: subprocess.run([pager], input=data.encode(), stdout=sys.stdout) class TablePrinter: def __init__(self): self._headers = {} self._mappers = defaultdict(list) self._right_aligned = {} self._widths = {} def set_field_properties( self, field, mappers=None, max_width=None, right_align=False, header=None, ): if mappers: self._mappers[field] = mappers if max_width: self._widths[field] = max_width self._right_aligned[field] = right_align self._headers[field] = header if header else field.upper() def print(self, fields, items, file=sys.stdout): headers = [self._headers.get(field, field.upper()) for field in fields] widths = [len(field) for field in headers] rows = [] for item in items: row = [] for i, field in enumerate(fields): v = item.get(field) for mapper in self._mappers[field]: v = mapper(v) v = str(v) if len(v) > widths[i]: max_width = self._widths.get(field) if max_width and len(v) > max_width: v = v[: max_width - 3] + "..." widths[i] = len(v) row.append(v) rows.append(row) print(self._format_row(headers, widths, False)) for row in rows: print( self._format_row( row, widths, self._right_aligned.get(field, False) ) ) @staticmethod def _add_padding(value, width, right_align): if right_align: return value.rjust(width) return value.ljust(width) @staticmethod def _format_row(columns, widths, right_aligned): cols = [ TablePrinter._add_padding(value, widths[i], right_aligned) for i, value in enumerate(columns) ] return " ".join(cols) def n_a(value): return value if value is not None else "n/a" def to_int(value): return int(value) if value is not None else value def extract_field(field): def extractor(value): if isinstance(value, dict): return value.get(field) return value return extractor def print_table(data, field_props): table = TablePrinter() for props in field_props: table.set_field_properties(*props) table.print([item[0] for item in field_props], data) def print_incidents(data): field_props = ( ("timestamp", [to_int]), ("abuser", [n_a]), ("country", [extract_field("code")]), ("times", [n_a]), ("name", [n_a]), ("severity", [n_a]), ) print_table(data, field_props) def add_ttl(data): now = int(time.time()) for item in data: expiration = item.get("expiration", 0) if expiration > 0: item["ttl"] = expiration - now else: item["ttl"] = 0 def print_graylist(data): add_ttl(data) field_props = ( ("ip",), ("ttl",), ("country", [extract_field("code")]), ) print_table(data, field_props) def print_bwlist(data): add_ttl(data) field_props = ( ("ip",), ("ttl",), ("country", [extract_field("code")]), ("imported_from",), ("comment",), ) print_table(data, field_props) def guess_printer(data): if isinstance(data, (list, tuple)): if len(data): printer = TablePrinter() if isinstance(data[0], dict): keys = sorted(data[0].keys()) printer.set_field_properties( "country", mappers=[extract_field("code")] ) printer.print(keys, data) else: for item in data: print(item) else: print(data) def yaml_printer(data): if isinstance(data, str): print(data) else: print(yaml.dump(data, default_flow_style=False)) def json_printer(data): if isinstance(data, str): print(data) else: print(json.dumps(data)) def hook_printer(data): if isinstance(data, dict): print("Status: {}".format(data["status"])) else: result = [] for hook in data: result.append( "Event: {}, Path: {}{}".format( hook["event"], hook["path"], " native" if hook["native"] else "", ) ) print("\n".join(result)) def waf_set_printer(items): if not items: print("No users targeted.") return counts = {"succeeded": 0, "skipped": 0, "failed": 0} for item in items: counts[item["status"]] += 1 print( "{succeeded} succeeded, {skipped} skipped, {failed} failed.".format( **counts ) ) print() print_table(items, (("user",), ("status",), ("reason",))) def waf_status_printer(result): header = "Global WAF: " + result.get("global_waf", "unknown") if not result.get("security_plugin_enabled", True): header += " (plugin off)" print(header) print( "Default (no override): " + result.get("global_waf_default", "unknown") ) items = result.get("items") or [] total = result.get("total_count", len(items)) if len(items) < total: # Page or 500-cap truncated the list — make the gap explicit so a # human doesn't read the table as the complete set. print("Total accounts: {} (showing {})".format(total, len(items))) else: print("Total accounts: {}".format(total)) print() if not items: print("No accounts.") return print_table( items, (("name",), ("waf_status",), ("source",), ("wp_sites",)), ) PRINTERS = { ("config", "show"): json_printer, ("eula", "show"): pager, ("get",): print_incidents, ("whitelist",): print_bwlist, ("whitelist", "ip", "list"): print_bwlist, ("blacklist",): print_bwlist, ("blacklist", "ip", "list"): print_bwlist, ("graylist",): print_graylist, ("graylist", "ip", "list"): print_graylist, ("malware", "on-demand", "status"): yaml_printer, ("feature-management", "defaults"): yaml_printer, ("feature-management", "show"): yaml_printer, ("feature-management", "enable"): yaml_printer, ("feature-management", "disable"): yaml_printer, ("feature-management", "get"): yaml_printer, ("hook", "add"): hook_printer, ("hook", "delete"): hook_printer, ("hook", "list"): hook_printer, ("hook", "add-native"): hook_printer, ("wordpress-plugin", "waf", "set"): waf_set_printer, ("wordpress-plugin", "waf", "status"): waf_status_printer, } # Printers that consume the full response dict (globals + items), not just # result["items"] — needed for the header line the waf status table carries. _FULL_RESULT_PRINTERS = {("wordpress-plugin", "waf", "status")} def _get_default_output(result): return result["items"] if result.get("items") is not None else "OK" def _print_json_response(result, is_verbose=False): pretty_args = PRETTY_JSON_ARGS if is_verbose else {} print(json.dumps(result, **pretty_args)) def _print_plain_response(method, result): """Print result in plain text format using appropriate printer.""" print_fun = PRINTERS.get(method, guess_printer) if method in _FULL_RESULT_PRINTERS: print_fun(result) else: print_fun(_get_default_output(result)) def print_response(method, result, is_json=False, is_verbose=False): if is_json: _print_json_response(result, is_verbose) else: _print_plain_response(method, result) def print_warnings(data: dict): if not isinstance(data, dict): # This can happen, for example, if validation of cli args fails return for warning in data.get("warnings", []): print(warning, file=sys.stderr) def print_error( result, messages, is_json=False, is_verbose=False, *, file=sys.stderr ): if is_json: pretty_args = PRETTY_JSON_ARGS if is_verbose else {} print(json.dumps({result: messages}, **pretty_args)) else: if isinstance(messages, (list, tuple)): for msg in messages: print("%s: %s" % (_CLI_MSG_PREFIX[result], msg), file=file) else: print(messages, file=file) defence360agent/utils/common.py0000644000000000000000000003464500000000000013564 0ustar import asyncio import datetime import functools import logging import socket import time import re import os import sys MINUTE = datetime.timedelta(minutes=1).total_seconds() HOUR = datetime.timedelta(hours=1).total_seconds() DAY = datetime.timedelta(days=1).total_seconds() WEEK = datetime.timedelta(weeks=1).total_seconds() logger = logging.getLogger(__name__) class ServiceBase(object): """Base service class.""" def __init__(self, loop): self._loop = loop self._should_stop = False self._main_task = None self._state = self.StoppedState(self) def start(self): return self._state.start() def should_stop(self): return self._state.should_stop() async def wait(self): return await self._state.wait() def is_running(self): return self._state.is_running() async def _run(self): raise NotImplementedError class State(object): def __init__(self, obj): """:type obj: ServiceBase""" self._obj = obj def start(self): pass def should_stop(self): pass async def wait(self): task = self._obj._main_task if task: await task def is_running(self): return False class StoppedState(State): def _on_stop(self, future): self._obj._state = ServiceBase.StoppedState(self._obj) self._obj._should_stop = False def start(self): obj = self._obj obj._main_task = obj._loop.create_task(obj._run()) obj._main_task.add_done_callback(self._on_stop) obj._state = ServiceBase.RunningState(obj) class RunningState(State): def should_stop(self): obj = self._obj obj._should_stop = True obj._main_task.cancel() obj._state = ServiceBase.StoppingState(obj) def is_running(self): return True class StoppingState(State): def start(self): raise ProgrammingError( "Cannot start stopping service. Please wait while it stop." ) class ProgrammingError(Exception): pass class RateLimit: """Decorator to limit function calls to one per *period* seconds. If less than *period* seconds have passed since the last call, then the request to call the function is replace with an *on_drop* call with the same arguments. If *on_drop* is None [default] then the call is just dropped """ def __init__(self, period, timer=time.monotonic, *, on_drop=None): self._next_call_time = None self._period = period self._timer = timer self._on_drop = on_drop @property def should_be_called(self): return ( self._next_call_time is None or self._next_call_time <= self._timer() ) def __call__(self, func): @functools.wraps(func) def wrapper(*args, **kwargs): if self.should_be_called: self._next_call_time = self._timer() + self._period return func(*args, **kwargs) elif self._on_drop is not None: return self._on_drop(*args, **kwargs) @functools.wraps(func) async def async_wrapper(*args, **kwargs): if self.should_be_called: self._next_call_time = self._timer() + self._period return await func(*args, **kwargs) elif self._on_drop is not None: return self._on_drop(*args, **kwargs) return async_wrapper if asyncio.iscoroutinefunction(func) else wrapper rate_limit = RateLimit class CoalesceCalls: def __init__(self): self.call_time = float("-inf") self.delayed_call = None def coalesce_calls(self, period, *, done_callback=None): """ Decorator to coalesce coroutine calls to one per *period* seconds. Requests for a coroutine call in a given time period are coalesced: If t is the time of the last call, then N call requests in the [t, t+period) time interval results in a single call at the t+period time iff N>0 i.e., if less than *period* seconds have passed since the last call, then the calls are coalesced: (N-1) requests are dropped, Nth requests is performed in *period* seconds. It is unspecified which exact call is made if arguments differ. If the call is not dropped then *done_callback* is attached to the task when the coroutine is scheduled with the event loop. Given `c` is the time of the last [actual] call (`loop.create_task()`) And `T` is the coalesce time period When a call request arrives at `t` time Then | call pending? | t>c+T | c<=t<=c+T | t (self.call_time + period): # call immediately if self.delayed_call is not None: # get string representation for logs # before cancelling the call old_delayed_call_repr = str(self.delayed_call) self.delayed_call.cancel() self.delayed_call = None logger.warning( "There was a scheduled call (%s)" " but more than period (%r) seconds passed" " since the last call (%r, now=%r)", old_delayed_call_repr, period, self.call_time, now, ) logger.info( "Satisfy the call request soon: %s. No calls in" " more than %r seconds since the start", call_repr, period, ) self.delayed_call = loop.call_soon( call_delayed, coro, args, kwargs ) elif self.call_time <= now <= (self.call_time + period): delay = (self.call_time + period) - now if self.delayed_call is not None: # drop call request logger.info( "Drop call request for %s" ", enforcing one call per %r seconds limit" ". Next call is in ~%.2f seconds", call_repr, period, delay, ) else: # schedule call request assert self.delayed_call is None logger.info( "Delay call request: %s for ~%.2f seconds" ". Enforcing one call per %r seconds limit", call_repr, delay, period, ) self.delayed_call = loop.call_at( self.call_time + period, call_delayed, coro, args, kwargs, ) else: # now < call_time logger.warning( "Drop call request for %s, reason: last call time" " (%r, now=%r) is in the future", call_repr, self.call_time, now, ) return wrapper return decorator webserver_gracefull_restart = CoalesceCalls() def get_hostname(): """Returns readable name of the server. It is sent to CLN and allows user to sort out his servers. """ hostname = socket.getfqdn() if hostname is None or hostname.lower().startswith("localhost"): return socket.gethostname() return hostname # Everything from there is copied from setuptools package # Copied from setuptools/_distutils/version.py class Version: """Abstract base class for version numbering classes. Just provides constructor (__init__) and reproducer (__repr__), because those seem to be the same for all version numbering classes; and route rich comparisons to _cmp. """ def __init__(self, vstring=None): if vstring: self.parse(vstring) def __repr__(self): return "{} ('{}')".format(self.__class__.__name__, str(self)) def __eq__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c == 0 def __lt__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c < 0 def __le__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c <= 0 def __gt__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c > 0 def __ge__(self, other): c = self._cmp(other) if c is NotImplemented: return c return c >= 0 # Copied from setuptools/_distutils/version.py class LooseVersion(Version): """Version numbering for anarchists and software realists. Implements the standard interface for version number classes as described above. A version number consists of a series of numbers, separated by either periods or strings of letters. When comparing version numbers, the numeric components will be compared numerically, and the alphabetic components lexically. The following are all valid version numbers, in no particular order: 1.5.1 1.5.2b2 161 3.10a 8.02 3.4j 1996.07.12 3.2.pl0 3.1.1.6 2g6 11g 0.960923 2.2beta29 1.13++ 5.5.kw 2.0b1pl0 In fact, there is no such thing as an invalid version number under this scheme; the rules for comparison are simple and predictable, but may not always give the results you want (for some definition of "want"). """ component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE) def parse(self, vstring): # I've given up on thinking I can reconstruct the version string # from the parsed tuple -- so I just store the string here for # use by __str__ self.vstring = vstring components = [ x for x in self.component_re.split(vstring) if x and x != "." ] for i, obj in enumerate(components): try: components[i] = int(obj) except ValueError: pass self.version = components def __str__(self): return self.vstring def __repr__(self): return "LooseVersion ('%s')" % str(self) def _cmp(self, other): if isinstance(other, str): other = LooseVersion(other) elif not isinstance(other, LooseVersion): return NotImplemented if self.version == other.version: return 0 if self.version < other.version: return -1 if self.version > other.version: return 1 # Copied from setuptools/_distutils/spawn.py def find_executable(executable, path=None): """Tries to find 'executable' in the directories listed in 'path'. A string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']. Returns the complete filename or None if not found. """ _, ext = os.path.splitext(executable) if (sys.platform == "win32") and (ext != ".exe"): executable = executable + ".exe" if os.path.isfile(executable): return executable if path is None: path = os.environ.get("PATH", None) if path is None: try: path = os.confstr("CS_PATH") except (AttributeError, ValueError): # os.confstr() or CS_PATH is not available path = os.defpath # bpo-35755: Don't use os.defpath if the PATH environment variable is # set to an empty string # PATH='' doesn't match, whereas PATH=':' looks in the current directory if not path: return None paths = path.split(os.pathsep) for p in paths: f = os.path.join(p, executable) if os.path.isfile(f): # the file exists, we have a shot at spawn working return f return None defence360agent/utils/completions.py0000644000000000000000000002340400000000000014617 0ustar """ Shell auto-completion script generators for the CLI. Introspects an argparse parser to enumerate all commands, subcommands, and flags, then emits completion scripts for bash, zsh, and fish. """ import argparse import re from typing import Dict, List, Tuple def _safe_identifier(prog: str) -> str: """Convert a prog name to a safe shell identifier (letters, digits, _).""" return re.sub(r"[^a-zA-Z0-9]", "_", prog) def _collect_commands( parser: argparse.ArgumentParser, ) -> Dict[Tuple[str, ...], List[str]]: """Walk the parser tree and return {command_path: [flags]} mapping.""" result: Dict[Tuple[str, ...], List[str]] = {} _walk_parser(parser, (), result) return result def _get_flags(parser: argparse.ArgumentParser) -> List[str]: """Extract all optional flags from a parser (excluding help).""" flags = [] for action in parser._actions: if isinstance(action, argparse._HelpAction): continue if isinstance(action, argparse._SubParsersAction): continue for opt in action.option_strings: flags.append(opt) return sorted(flags) def _walk_parser( parser: argparse.ArgumentParser, path: Tuple[str, ...], result: Dict[Tuple[str, ...], List[str]], ): """Recursively walk subparsers and collect command paths + flags.""" flags = _get_flags(parser) result[path] = flags for action in parser._actions: if isinstance(action, argparse._SubParsersAction): for name, subparser in action.choices.items(): _walk_parser(subparser, path + (name,), result) def _get_subcommands( commands: Dict[Tuple[str, ...], List[str]], prefix: Tuple[str, ...], ) -> List[str]: """Get immediate subcommands of a given prefix.""" subs = set() for path in commands: if len(path) == len(prefix) + 1 and path[: len(prefix)] == prefix: subs.add(path[-1]) return sorted(subs) def generate_bash( parser: argparse.ArgumentParser, prog: str = "imunify360-agent" ) -> str: """Generate a bash completion script.""" commands = _collect_commands(parser) lines = [] lines.append(f"# bash completion for {prog}") lines.append(f"# Auto-generated by {prog} completions bash") lines.append("") lines.append(f"_{_safe_identifier(prog)}_completions() {{") lines.append(" local cur prev words cword") lines.append(" if type _init_completion &>/dev/null; then") lines.append(" _init_completion || return") lines.append(" else") lines.append(" COMPREPLY=()") lines.append(' cur="${COMP_WORDS[COMP_CWORD]}"') lines.append(' prev="${COMP_WORDS[COMP_CWORD-1]}"') lines.append(' words=("${COMP_WORDS[@]}")') lines.append(" cword=$COMP_CWORD") lines.append(" fi") lines.append("") lines.append(" # Build the command path from words") lines.append(' local cmd_path=""') lines.append(" local i") lines.append(" for (( i=1; i < cword; i++ )); do") lines.append(' case "${words[i]}" in') lines.append(" -*) continue ;;") lines.append( ' *) cmd_path="${cmd_path:+${cmd_path} }${words[i]}" ;;' ) lines.append(" esac") lines.append(" done") lines.append("") lines.append(' case "$cmd_path" in') # Sort by depth (deepest first) so more specific paths match first all_paths = sorted(commands.keys(), key=lambda p: (-len(p), p)) for path in all_paths: if not path: continue subs = _get_subcommands(commands, path) flags = commands[path] completions = " ".join(subs + flags) pattern = " ".join(path) lines.append(f' "{pattern}")') lines.append( f' COMPREPLY=($(compgen -W "{completions}" -- "$cur"))' ) lines.append(" return ;;") # Root level root_subs = _get_subcommands(commands, ()) root_flags = commands.get((), []) root_completions = " ".join(root_subs + root_flags) lines.append(' "")') lines.append( f' COMPREPLY=($(compgen -W "{root_completions}" -- "$cur"))' ) lines.append(" return ;;") lines.append(" esac") lines.append("}") lines.append("") lines.append(f"complete -F _{_safe_identifier(prog)}_completions {prog}") lines.append("") return "\n".join(lines) def generate_zsh( parser: argparse.ArgumentParser, prog: str = "imunify360-agent" ) -> str: """Generate a zsh completion script.""" commands = _collect_commands(parser) func_name = f"_{_safe_identifier(prog)}" lines = [] lines.append(f"#compdef {prog}") lines.append(f"# zsh completion for {prog}") lines.append(f"# Auto-generated by {prog} completions zsh") lines.append("") lines.append(f"{func_name}() {{") lines.append(" local -a commands flags") lines.append(" local cmd_path") lines.append("") lines.append(" # Build command path from words") lines.append(" cmd_path=()") lines.append(" for word in ${words[2,-1]}; do") lines.append(" [[ $word == -* ]] && continue") lines.append(' [[ $word == "$words[$CURRENT]" ]] && continue') lines.append(" cmd_path+=($word)") lines.append(" done") lines.append("") lines.append(' case "${cmd_path[*]}" in') all_paths = sorted(commands.keys(), key=lambda p: (-len(p), p)) for path in all_paths: if not path: continue subs = _get_subcommands(commands, path) flags = commands[path] pattern = " ".join(path) lines.append(f' "{pattern}")') if subs: desc_list = " ".join(f'"{s}"' for s in subs) lines.append(f" commands=({desc_list})") if flags: flag_list = " ".join(f'"{f}"' for f in flags) lines.append(f" flags=({flag_list})") lines.append( " _describe 'command' commands -- flags && return" if subs else f" compadd -- {' '.join(flags)} && return" ) lines.append(" ;;") # Root level root_subs = _get_subcommands(commands, ()) root_flags = commands.get((), []) root_desc = " ".join(f'"{s}"' for s in root_subs) lines.append(' "")') lines.append(f" commands=({root_desc})") if root_flags: flag_list = " ".join(f'"{f}"' for f in root_flags) lines.append(f" flags=({flag_list})") lines.append(" _describe 'command' commands -- flags && return") lines.append(" ;;") lines.append(" esac") lines.append("}") lines.append("") lines.append(f"{func_name}") lines.append("") return "\n".join(lines) def generate_fish( parser: argparse.ArgumentParser, prog: str = "imunify360-agent" ) -> str: """Generate a fish completion script.""" commands = _collect_commands(parser) lines = [] lines.append(f"# fish completion for {prog}") lines.append(f"# Auto-generated by {prog} completions fish") lines.append("") # For each command path, emit completions # Fish uses conditions based on what subcommands have been entered for path in sorted(commands.keys(), key=lambda p: (len(p), p)): subs = _get_subcommands(commands, path) flags = commands[path] if not path: # Root level subcommands condition = ( "not __fish_seen_subcommand_from" f" {' '.join(_get_subcommands(commands, ()))}" ) for sub in subs: lines.append( f"complete -c {prog} -n '{condition}' -f -a '{sub}'" ) for flag in flags: if flag.startswith("--"): lines.append( f"complete -c {prog} -n '{condition}' -l '{flag[2:]}'" ) elif flag.startswith("-"): lines.append( f"complete -c {prog} -n '{condition}' -s '{flag[1:]}'" ) else: # Build condition: must have seen parent commands but not children seen_parts = [] for p in path: seen_parts.append(f"__fish_seen_subcommand_from {p}") condition = " && ".join(seen_parts) child_subs = subs if child_subs: condition += ( " && not __fish_seen_subcommand_from" f" {' '.join(child_subs)}" ) for sub in subs: lines.append( f"complete -c {prog} -n '{condition}' -f -a '{sub}'" ) for flag in flags: if flag.startswith("--"): lines.append( f"complete -c {prog} -n '{condition}' -l '{flag[2:]}'" ) elif flag.startswith("-"): lines.append( f"complete -c {prog} -n '{condition}' -s '{flag[1:]}'" ) lines.append("") return "\n".join(lines) GENERATORS = { "bash": generate_bash, "zsh": generate_zsh, "fish": generate_fish, } SUPPORTED_SHELLS = sorted(GENERATORS.keys()) def generate_completions( parser: argparse.ArgumentParser, shell: str, prog: str = "imunify360-agent", ) -> str: """Generate completion script for the given shell. Raises ValueError if shell is not supported. """ generator = GENERATORS.get(shell) if generator is None: raise ValueError( f"Unsupported shell: {shell}. " f"Supported shells: {', '.join(SUPPORTED_SHELLS)}" ) return generator(parser, prog) defence360agent/utils/config.py0000644000000000000000000000323700000000000013532 0ustar import asyncio import copy import time from logging import getLogger from defence360agent.contracts import config, messages from defence360agent.feature_management import checkers CONFIG_UPDATE_TIMEOUT = config.SimpleRpc.CLIENT_TIMEOUT / 2 logger = getLogger(__name__) OBSOLETE_SECTION = "KERNELCARE" OBSOLETE_OPTION = "edf" def warn_obsolete_option(data): if OBSOLETE_OPTION in data.get(OBSOLETE_SECTION, dict()): logger.warning( "Configuration update with an obsolete kernelcare option 'edf'." " This option has no effect." ) def enforce_waf_optin_policy(data): wordpress = data.get("WORDPRESS") if not isinstance(wordpress, dict): return if ( config.caller_type.get() == config.UserType.NON_ROOT and wordpress.get("waf_enabled") is True and not config.Wordpress.WAF_DEFAULT ): wordpress.pop("waf_enabled", None) if not wordpress: data.pop("WORDPRESS", None) async def update_config(sink, data, user=None): warn_obsolete_option(data) checkers.config_validation(data, user) enforce_waf_optin_policy(data) conf = config.ConfigFile(user) conf.dict_to_config(data, without_defaults=True) updated = asyncio.Event() await sink.process_message( messages.ConfigUpdate( conf=conf, timestamp=time.time(), event=updated, # Snapshot so a caller that reuses/mutates the delta after this # returns cannot alter what a handler reads as "submitted". submitted=copy.deepcopy(data), ) ) await asyncio.wait_for(updated.wait(), timeout=CONFIG_UPDATE_TIMEOUT) defence360agent/utils/cronjob.py0000644000000000000000000000160600000000000013717 0ustar from typing import Union, Optional class CronJob(object): __slots__ = "minute", "hour", "cmd" def __init__( self, *, minute: Union[int, str, None], hour: Union[int, str, None], cmd: Optional[str], ): self.minute = minute self.hour = hour self.cmd = cmd def __str__(self): return ( "# DO NOT EDIT. AUTOMATICALLY GENERATED BY IMUNIFY360." f"\n{self.minute} {self.hour} * * * root {self.cmd}\n" ) @classmethod def from_str(cls, data): minute = hour = cmd = None lines = [x for x in data.splitlines() if x[0] != "#"] if lines: line_members = lines[0].split(" ") minute = line_members[0] hour = line_members[1] cmd = " ".join(line_members[6:]) return CronJob(minute=minute, hour=hour, cmd=cmd) defence360agent/utils/doctor.py0000644000000000000000000001257700000000000013566 0ustar import asyncio import logging import os import shutil import stat import tempfile import urllib.request from pathlib import Path from typing import Optional from defence360agent.contracts.config import Packaging from defence360agent.subsys.persistent_state import save_state from defence360agent.utils import CheckRunError, check_run _HTTP_TIMEOUT = 30 logger = logging.getLogger(__name__) _SCRIPT_NAME = "imunify-doctor.sh" _SCRIPT_URL = ( "https://repo.imunify360.cloudlinux.com/defence360/" + _SCRIPT_NAME ) _SIG_URL = _SCRIPT_URL + ".sig" _TMPDIR = Path("/var/imunify360/tmp") _PUBKEY_PATHS = ( Path("/etc/pki/rpm-gpg/RPM-GPG-KEY-CloudLinux-Imunify"), Path("/etc/apt/trusted.gpg.d/RPM-GPG-KEY-CloudLinux.gpg"), ) def _find_pubkey() -> Optional[Path]: for p in _PUBKEY_PATHS: if p.is_file() and os.access(str(p), os.R_OK): return p return None def _blocking_download(url: str, dst: Path) -> None: req = urllib.request.Request(url) with urllib.request.urlopen(req, timeout=_HTTP_TIMEOUT) as resp, dst.open( "wb" ) as fp: shutil.copyfileobj(resp, fp) def _blocking_setup_workdir(): """Locate the pubkey + gpg binary and create a validated 0700 workdir. Returns (pubkey_path, workdir_path) on success or None on failure; any partial state is removed before returning. """ pubkey = _find_pubkey() if pubkey is None or not shutil.which("gpg"): return None try: _TMPDIR.mkdir(mode=0o700, parents=True, exist_ok=True) workdir = Path( tempfile.mkdtemp(prefix="imunify-doctor.", dir=str(_TMPDIR)) ) except OSError as exc: logger.info("cannot prepare workdir under %s: %s", _TMPDIR, exc) return None try: # Single lstat — atomic snapshot of mode + uid. Path.is_dir() would # follow symlinks and Path.is_symlink() would issue another lstat, so # using st.st_mode here both eliminates the extra syscalls and keeps # the symlink rejection semantically consistent with the lstat. st = workdir.lstat() if ( stat.S_ISLNK(st.st_mode) or not stat.S_ISDIR(st.st_mode) or st.st_uid != os.geteuid() ): shutil.rmtree(str(workdir), ignore_errors=True) return None (workdir / "gnupg").mkdir(mode=0o700) except OSError as exc: logger.info("workdir setup failed: %s", exc) shutil.rmtree(str(workdir), ignore_errors=True) return None return pubkey, workdir def _blocking_rmtree(p: Path) -> None: shutil.rmtree(str(p), ignore_errors=True) def _blocking_chmod(p: Path, mode: int) -> None: p.chmod(mode) async def _download(url: str, dst: Path) -> None: """Fetch *url* to *dst* without blocking the event loop. Raises urllib.error.URLError (subclass of OSError) on any HTTP/transport error, which the caller's `except OSError` already handles. """ loop = asyncio.get_event_loop() await loop.run_in_executor(None, _blocking_download, url, dst) async def _verified_remote_script() -> Optional[Path]: """ Download imunify-doctor.sh + .sig into /var/imunify360/tmp and verify the detached signature against an ephemeral keyring seeded with the CloudLinux pubkey. Returns the verified script on success or None on any failure (so the caller can fall back to the package copy). """ loop = asyncio.get_event_loop() setup = await loop.run_in_executor(None, _blocking_setup_workdir) if setup is None: return None pubkey, workdir = setup script = workdir / _SCRIPT_NAME sig = workdir / (_SCRIPT_NAME + ".sig") gpghome = workdir / "gnupg" success = False try: await _download(_SCRIPT_URL, script) await _download(_SIG_URL, sig) env = dict(os.environ, GNUPGHOME=str(gpghome)) await check_run( ["gpg", "--batch", "--quiet", "--import", str(pubkey)], env=env, ) await check_run( ["gpg", "--batch", "--quiet", "--verify", str(sig), str(script)], env=env, ) await loop.run_in_executor(None, _blocking_chmod, script, 0o700) success = True return script except (CheckRunError, OSError) as exc: logger.info("signed remote doctor fetch failed: %s", exc) return None finally: if not success: await loop.run_in_executor(None, _blocking_rmtree, workdir) async def _repo_get_doctor_key() -> str: script = await _verified_remote_script() if script is None: raise ValueError("Signed remote doctor script not available") loop = asyncio.get_event_loop() try: out = await check_run([str(script)]) finally: await loop.run_in_executor(None, _blocking_rmtree, script.parent) key = out.decode().strip() if not key: raise ValueError("Doctor key is empty") return key async def _package_get_doctor_key() -> str: dir_ = Packaging.DATADIR if not Path(dir_).is_dir(): dir_ = "/opt/imunify360/venv/share/imunify360" out = await check_run([Path(dir_, "scripts", _SCRIPT_NAME)]) key = out.decode().strip() return key async def get_doctor_key(): try: key = await _repo_get_doctor_key() except (CheckRunError, ValueError, OSError): key = await _package_get_doctor_key() save_state("doctor_key", {"doctor_key": key}) return key defence360agent/utils/fd_ops.py0000644000000000000000000001662500000000000013544 0ustar """fd-based file operations for symlink-attack mitigation. All helpers in this module use O_NOFOLLOW and dir_fd-relative syscalls so that no path-based resolution can be redirected by a concurrent symlink swap. This module is intentionally kept separate from utils/__init__.py to avoid loading these OS-specific helpers into every agent component. """ import errno import logging import os import stat from contextlib import contextmanager, suppress from pathlib import Path logger = logging.getLogger(__name__) def rmtree_fd(dir_fd) -> None: """Remove all contents of a directory using fd-relative operations. Every entry is opened with ``O_NOFOLLOW`` so symlinks inside the tree are unlinked rather than followed. The directory referenced by *dir_fd* itself is **not** removed — the caller should ``os.rmdir()`` the parent entry after this call returns. Uses an iterative approach with an explicit stack to avoid hitting Python's recursion limit on adversarial deeply-nested trees. *dir_fd* must be an open ``O_RDONLY | O_DIRECTORY`` descriptor. """ # Each stack frame is (fd, name_to_rmdir_after_close) where # name_to_rmdir_after_close is the entry name that should be # rmdir'd from the parent once this fd is fully processed. # The initial fd is managed by the caller, so its rmdir entry is None. stack = [(dir_fd, None)] try: while stack: current_fd, _ = stack[-1] pushed = False with os.scandir(current_fd) as entries: for entry in entries: if entry.is_dir(follow_symlinks=False): child_fd = os.open( entry.name, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=current_fd, ) stack.append((child_fd, entry.name)) pushed = True break # restart scan from the new directory else: os.unlink(entry.name, dir_fd=current_fd) if not pushed: # All entries in current directory have been removed. fd, name = stack.pop() if name is not None: # Close the child fd and rmdir it from the parent. os.close(fd) parent_fd, _ = stack[-1] os.rmdir(name, dir_fd=parent_fd) except BaseException: # On error, close any fds we opened (but not the caller's dir_fd). for fd, name in stack: if name is not None: os.close(fd) raise def open_dir_no_symlinks(path) -> int: """Open a directory, refusing symlinks at every path component. Walks the absolute *path* one component at a time, opening each with ``O_NOFOLLOW | O_DIRECTORY`` relative to the parent fd. This guards against symlink attacks at *any* depth in the hierarchy, not just the leaf. Returns an ``O_RDONLY`` file descriptor for the final directory. The caller is responsible for closing it. """ path = os.path.abspath(os.fspath(path)) parts = Path(path).parts # ('/', 'home', 'user', ...) fd = os.open(parts[0], os.O_RDONLY | os.O_DIRECTORY) try: for part in parts[1:]: new_fd = os.open( part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd, ) os.close(fd) fd = new_fd return fd except BaseException: os.close(fd) raise @contextmanager def open_nofollow(path, flags=os.O_RDONLY, *, dir_fd=None): """Open a file with O_NOFOLLOW, closing the fd on exit. Yields the raw file descriptor. Rejects symlinks at the leaf component (raises ELOOP). When *dir_fd* is provided, *path* is resolved relative to that directory descriptor. """ kw = {"dir_fd": dir_fd} if dir_fd is not None else {} fd = os.open(str(path), flags | os.O_NOFOLLOW, **kw) try: yield fd finally: os.close(fd) @contextmanager def safe_dir(path): """Open a directory with symlink protection, closing the fd on exit. Walks every path component with O_NOFOLLOW via open_dir_no_symlinks and yields the resulting fd. """ fd = open_dir_no_symlinks(path) try: yield fd finally: os.close(fd) def atomic_rewrite_fd( filename, data: bytes, *, uid, gid, allow_empty_content, permissions, dir_fd: int, ) -> bool: """dir_fd-relative implementation of atomic_rewrite. The caller opens the directory with O_NOFOLLOW before any file I/O begins. All file operations use dir_fd so that a concurrent rename of the directory to a symlink cannot redirect writes to a privileged path. """ _, basename = os.path.split(filename) # Read current content without following symlinks. try: content_fd = os.open( basename, os.O_RDONLY | os.O_NOFOLLOW, dir_fd=dir_fd ) with os.fdopen(content_fd, "rb") as f: old_content = f.read(len(data) + 1) if old_content == data: return False except FileNotFoundError: pass # file does not exist yet; will be created except OSError as exc: if exc.errno == errno.ELOOP: pass # existing entry is a symlink; overwrite it else: raise if not allow_empty_content and not data: logger.error("empty content: %r for file: %s", data, filename) return False if permissions is None: try: st = os.stat(basename, dir_fd=dir_fd, follow_symlinks=False) if stat.S_ISLNK(st.st_mode): raise OSError(errno.ELOOP, os.strerror(errno.ELOOP), basename) permissions = stat.S_IMODE(st.st_mode) except FileNotFoundError: current_umask = os.umask(0) os.umask(current_umask) permissions = 0o666 & ~current_umask # Create temp file atomically inside the directory referenced by dir_fd. # O_NOFOLLOW + O_EXCL ensures the name cannot be a pre-existing symlink. tmp_basename = None tmp_fd = -1 for _ in range(100): tmp_basename = f"{basename}_{os.urandom(4).hex()}.i360edit" try: tmp_fd = os.open( tmp_basename, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=dir_fd, ) break except FileExistsError: continue else: raise FileExistsError("Could not create temporary file (100 attempts)") try: view = memoryview(data) written = 0 while written < len(data): written += os.write(tmp_fd, view[written:]) if uid is not None and gid is not None: os.chown(tmp_fd, uid, gid) os.chmod(tmp_fd, permissions) os.fsync(tmp_fd) os.close(tmp_fd) tmp_fd = -1 # Atomic rename entirely within the directory we hold open. os.rename(tmp_basename, basename, src_dir_fd=dir_fd, dst_dir_fd=dir_fd) tmp_basename = None # rename succeeded; no cleanup needed finally: if tmp_fd >= 0: os.close(tmp_fd) if tmp_basename is not None: with suppress(FileNotFoundError): os.unlink(tmp_basename, dir_fd=dir_fd) return True defence360agent/utils/hyperscan.py0000644000000000000000000000022500000000000014253 0ustar import functools @functools.lru_cache(maxsize=1) def is_ssse3_supported(): with open("/proc/cpuinfo") as f: return "ssse3" in f.read() defence360agent/utils/importer.py0000644000000000000000000000524200000000000014124 0ustar """ Provides utilities for dynamically loading packages/modules. """ import importlib import importlib.util import logging import pkgutil from pathlib import Path from typing import Generator, List, Union logger = logging.getLogger(__name__) def get_module_by_path( module_name: str, file_path: Union[str, Path] ) -> "module": # noqa: F821 """ Execute and return module from *file_path* """ # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def iter_modules( paths: List[Union[str, Path]] ) -> Generator["module", None, None]: # noqa: F821 """ Yields all modules from *paths* """ for module in pkgutil.iter_modules(paths): if not module.ispkg: path = Path(module.module_finder.path) / f"{module.name}.py" yield get_module_by_path(module.name, path) def load(name: str, missing_ok=False) -> None: """ Import *name* module, if *name* is a package import all submodules. If *name* module/package is not found: - raise ModuleNotFoundError if *missing_ok* is False - ignore it if *missing_ok* is True """ try: spec = importlib.util.find_spec(name) except ModuleNotFoundError: if not missing_ok: raise return # import *name* itself, for package it is __init__.py importlib.import_module(name) if spec.loader.is_package(spec.name): package = name for module in pkgutil.iter_modules(spec.submodule_search_locations): importlib.import_module(f"{package}.{module.name}") def load_packages(packages: tuple, missing_ok=False) -> None: for package in packages: load(package, missing_ok=missing_ok) def get(*, module, name, default): """ Return object with *name* from specific *module*. If object was not found return *default* """ try: m = importlib.import_module(module) except ImportError: return default return getattr(m, name, default) def exists(name): try: spec = importlib.util.find_spec(name) except ModuleNotFoundError: return False return spec is not None class LazyImport: def __init__(self, module_name: str): self._module_name = module_name self._module = None @property def module(self): if self._module is None: self._module = importlib.import_module(self._module_name) return self._module def __getattr__(self, attr): return getattr(self.module, attr) defence360agent/utils/ipecho.py0000644000000000000000000000625700000000000013541 0ustar """IPEchoAPI - returns real IP address of the host (behind NAT)""" import asyncio import functools import logging import time import urllib from pathlib import Path from typing import Optional from async_lru import alru_cache from defence360agent.api.server import API, APIError from defence360agent.utils import atomic_rewrite from defence360agent.utils.validate import IP, IPVersion logger = logging.getLogger(__name__) TIMEOUT_FOR_IPECHO_REQUEST = 5 # in seconds CACHE_TTL_SECONDS = 3 * 60 * 60 CACHE_FILE_PATH = Path("/var/imunify360") / "ipecho_cache" class IPEchoAPI(API): """Make requests to the API for obtain own IP address""" URL = "/api/ip" @classmethod @alru_cache(maxsize=3) async def get_ip(cls, ip_version: IPVersion = None) -> Optional[str]: """Return cached result for resolved IP from echo ip API""" return await cls.ip_for_version(ip_version) @classmethod @functools.lru_cache(maxsize=1) def server_ip(cls): """Return cached result for resolved IP from echo ip API""" try: return cls._get_ip() except Exception as e: raise APIError from e @classmethod async def ip_for_version( cls, ip_version: IPVersion = None ) -> Optional[str]: """Return resolved IP from echo ip API""" loop = asyncio.get_event_loop() try: ip = await asyncio.wait_for( loop.run_in_executor(None, cls._get_ip), timeout=TIMEOUT_FOR_IPECHO_REQUEST, ) if IP.type_of(ip) != ip_version: raise ValueError("Wrong ip type") return ip except (asyncio.TimeoutError, ValueError) as e: raise APIError from e @classmethod def _load_cache(cls) -> Optional[str]: try: if not CACHE_FILE_PATH.exists(): return None mtime = CACHE_FILE_PATH.stat().st_mtime cache_age = time.time() - mtime if cache_age < 0: return None if cache_age < CACHE_TTL_SECONDS: ip = CACHE_FILE_PATH.read_text().strip() return ip else: return None except Exception as e: logger.error("IPEchoAPI cache read error: %s", e) return None @classmethod def _save_cache(cls, ip: str) -> None: try: atomic_rewrite( CACHE_FILE_PATH, ip, backup=False, permissions=0o644, ) except Exception as e: logger.error("IPEchoAPI cache write error: %s", e) @classmethod def _get_ip(cls): """Get IP from file-based cache or send request to API and process response.""" cached_ip = cls._load_cache() if cached_ip is not None: return cached_ip request = urllib.request.Request(cls._BASE_URL + cls.URL) response = cls.request(request) if response.get("status") != "ok": # time inside sync executor raise APIError("Unexpected API error") ip = response.get("ip") if ip: cls._save_cache(ip) return ip defence360agent/utils/json.py0000644000000000000000000000167100000000000013236 0ustar """JSON encoders to help with sending messages to server.""" import json from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network from playhouse.shortcuts import model_to_dict from defence360agent.model import Model def ip_net_to_string(net) -> str: """ IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' """ if not int(net.hostmask): return str(net.network_address) return str(net) class IPEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, (IPv4Network, IPv6Network)): return ip_net_to_string(obj) if isinstance(obj, (IPv4Address, IPv6Address)): return str(obj) return json.JSONEncoder.default(self, obj) class ServerJSONEncoder(IPEncoder): def default(self, obj): if isinstance(obj, Model): return model_to_dict(obj) return super().default(obj) defence360agent/utils/kwconfig.py0000644000000000000000000000333400000000000014072 0ustar import re from typing import Optional from defence360agent.utils import atomic_rewrite class KWConfig: """ Basic class for working with key-value configuration files Subclasses must define SEARCH_PATTERN and WRITE_PATTERN attributes """ SEARCH_PATTERN = DEFAULT_FILENAME = WRITE_PATTERN = "" ALLOW_EMPTY_CONFIG = True def __init__(self, name, filename=None): assert self.SEARCH_PATTERN self._pattern = re.compile( self.SEARCH_PATTERN.format(name), re.MULTILINE ) self._filename = filename or self.DEFAULT_FILENAME self._name = name def set(self, value) -> Optional[str]: assert self.WRITE_PATTERN with open(self._filename) as f: content = f.read() old_value = self._parse(content) if old_value is None: # If no variable found, just add to the bottom content += ( "\n" + self.WRITE_PATTERN.format(self._name, value) + "\n" ) else: content = self._pattern.sub( self.WRITE_PATTERN.format(self._name, value), content ) atomic_rewrite( self._filename, content, allow_empty_content=self.ALLOW_EMPTY_CONFIG, ) return old_value def get(self) -> Optional[str]: with open(self._filename) as f: content = f.read() return self._parse(content) def _parse(self, content) -> Optional[str]: match = self._pattern.search(content) return match and match.group(1) class PureFTPBaseConfig(KWConfig): SEARCH_PATTERN = r"^\s*?{}\s+(.*?)\s*?$" WRITE_PATTERN = "{} {}" DEFAULT_FILENAME = "/etc/pure-ftpd.conf" defence360agent/utils/net.py0000644000000000000000000000112300000000000013043 0ustar from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network from typing import Tuple, Union TCP = "tcp" IN, OUT = "in", "out" def pack_ip_address(ip_address: Union[IPv4Address, IPv6Address]): if ip_address.version == 6: return int.from_bytes(ip_address.packed[:8], "big", signed=True) else: return int(ip_address) def pack_ip_network( ip_network: Union[IPv4Network, IPv6Network] ) -> Tuple[int, int, int]: net = pack_ip_address(ip_network.network_address) mask = pack_ip_address(ip_network.netmask) return net, mask, ip_network.version defence360agent/utils/net_transport.py0000644000000000000000000003016000000000000015162 0ustar """Networking transport helpers for urllib. This module provides a small abstraction on top of urllib.request so that callers can keep using urllib.request.Request, but routing of connections can be customized: - hostname resolution is handled in user code; - selected IP may be randomized or chosen using any complex logic; - for HTTPS: connects to a chosen IP but keeps correct SNI and certificate hostname validation for the original hostname (NOT the IP). Examples: Default behavior (plain urllib): from defence360agent.utils.net_transport import UrlTransport transport = UrlTransport() req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Randomize target IP on each connection (A/AAAA -> random choice): from defence360agent.utils.net_transport import UrlTransport, RandomIpChooser chooser = RandomIpChooser() transport = UrlTransport(ip_chooser=chooser) req = urllib.request.Request( "https://files.imunify360.com/static/sigs/v1/description.json" ) with transport.open(req, timeout=10) as resp: body = resp.read() Notes: - HTTPS: connects to the chosen IP but keeps SNI/cert checks against original hostname. - HTTP: Host header stays original hostname because urllib builds it from the URL. """ import http.client import ipaddress import random import socket import threading import time import urllib.request from abc import ABC, abstractmethod from logging import getLogger from typing import Dict, Optional, Tuple, TYPE_CHECKING if TYPE_CHECKING: import ssl logger = getLogger(__name__) #: default cache TTL for DNS responses _DNS_DEFAULT_TTL_SECONDS = 300.0 def _is_ipv4(ip: str) -> bool: """Return True if *ip* is an IPv4 address string. Implementation relies solely on ipaddress.ip_address for correctness. """ try: return isinstance(ipaddress.ip_address(ip), ipaddress.IPv4Address) except ValueError: return False class IpChooser(ABC): """Select an IP address to connect to for a given hostname and port. Implementations may be stateful and can keep caches/metrics inside. """ @abstractmethod def choose(self, hostname: str, port: int) -> str: """Return an IP address (v4 or v6) for *hostname*:*port*.""" raise NotImplementedError def __call__(self, hostname: str, port: int) -> str: return self.choose(hostname, port) class DnsCacheResolver: """DNS cache for socket.getaddrinfo() results. It caches per (hostname, port, family). This is intentionally small and local: it is meant only to avoid excessive getaddrinfo() calls. """ def __init__( self, *, family: int = socket.AF_UNSPEC, ttl_seconds: float = _DNS_DEFAULT_TTL_SECONDS, ): self._family = family self._ttl_seconds = ttl_seconds self._cache: Dict[ Tuple[str, int, int], Tuple[float, Tuple[str, ...]] ] = {} self._lock = threading.Lock() def get_ips(self, hostname: str, port: int) -> Tuple[str, ...]: key = (hostname, port, self._family) now = time.time() with self._lock: cached = self._cache.get(key) if cached is not None: expires_at, ips = cached if now < expires_at: logger.debug( "DnsCacheResolver cache hit for %s:%s (family=%s)", hostname, port, self._family, ) return ips logger.debug( "DnsCacheResolver cache miss/expired for %s:%s (family=%s)", hostname, port, self._family, ) infos = socket.getaddrinfo( hostname, port, self._family, socket.SOCK_STREAM, ) ips = [] for _, _, _, _, sockaddr in infos: ip = sockaddr[0] if ip not in ips: ips.append(ip) if not ips: raise OSError("No IPs resolved for {}:{}".format(hostname, port)) ips_t = tuple(ips) with self._lock: self._cache[key] = (now + self._ttl_seconds, ips_t) logger.debug( "DnsCacheResolver resolved %s:%s (family=%s) to %s", hostname, port, self._family, ips_t, ) return ips_t class RandomIpChooserWithIPv6Toggle(IpChooser): """Resolve hostname and select a random IP. IPv6 selection can be enabled/disabled at runtime: - when IPv6 is enabled: choose from IPv4 + IPv6 candidates - when IPv6 is disabled: choose from IPv4-only candidates """ def __init__( self, *, resolver: Optional[DnsCacheResolver] = None, rng: Optional[random.Random] = None, ipv6_enabled: bool = True, ): self._resolver = resolver or DnsCacheResolver() self._rng = rng or random.Random() self._ipv6_enabled = ipv6_enabled self._last_ip: Optional[str] = None def enable_ipv6(self) -> None: self._ipv6_enabled = True def disable_ipv6(self) -> None: self._ipv6_enabled = False def is_ipv6_enabled(self) -> bool: return self._ipv6_enabled def last_ip(self) -> Optional[str]: return self._last_ip def last_ip_was_ipv6(self) -> bool: return bool(self._last_ip) and (":" in self._last_ip) def choose(self, hostname: str, port: int) -> str: ips = self._resolver.get_ips(hostname, port) if self._ipv6_enabled: chosen = self._rng.choice(ips) self._last_ip = chosen logger.debug( "RandomIpChooserWithIPv6Toggle selected IP %s for %s:%s " "(IPv6 enabled)", chosen, hostname, port, ) return chosen ipv4_ips = tuple(ip for ip in ips if _is_ipv4(ip)) if not ipv4_ips: raise OSError( "No IPv4 IPs resolved for {}:{}".format(hostname, port) ) chosen = self._rng.choice(ipv4_ips) self._last_ip = chosen logger.debug( "RandomIpChooserWithIPv6Toggle selected IPv4 IP %s for %s:%s " "(IPv6 disabled)", chosen, hostname, port, ) return chosen class RandomIpChooser(IpChooser): """Resolve hostname and select a random IP from resolved candidates.""" def __init__( self, *, resolver: Optional[DnsCacheResolver] = None, rng: Optional[random.Random] = None, ): self._resolver = resolver or DnsCacheResolver() self._rng = rng or random.Random() def choose(self, hostname: str, port: int) -> str: ips = self._resolver.get_ips(hostname, port) chosen = self._rng.choice(ips) logger.debug( "RandomIpChooser selected IP %s for %s:%s", chosen, hostname, port, ) return chosen class ForcedIPHTTPConnection(http.client.HTTPConnection): """HTTPConnection that connects to a chosen IP. Important: urllib builds the request URL with the original hostname, therefore the Host header stays correct. """ def __init__( self, hostname: str, port: Optional[int] = None, *, ip_chooser: IpChooser, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, ): super().__init__( hostname, port=port, timeout=timeout, source_address=source_address, ) self._ip_chooser = ip_chooser def connect(self) -> None: port = self.port or 80 ip = self._ip_chooser.choose(self.host, port) logger.debug( "ForcedIPHTTPConnection connecting to %s:%s for hostname %s", ip, port, self.host, ) self.sock = socket.create_connection( (ip, port), self.timeout, self.source_address, ) class ForcedIPHTTPSConnection(http.client.HTTPSConnection): """HTTPSConnection that connects to a chosen IP. TLS details: - Uses original hostname for SNI (server_hostname in wrap_socket) - Certificate hostname validation is performed for the original hostname """ def __init__( self, hostname: str, port: Optional[int] = None, *, ip_chooser: IpChooser, context: "ssl.SSLContext", timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, ): super().__init__( hostname, port=port, context=context, timeout=timeout, source_address=source_address, ) self._ip_chooser = ip_chooser def connect(self) -> None: port = self.port or 443 ip = self._ip_chooser.choose(self.host, port) logger.debug( "ForcedIPHTTPSConnection connecting to %s:%s for hostname %s", ip, port, self.host, ) raw_sock = socket.create_connection( (ip, port), self.timeout, self.source_address, ) if self._tunnel_host: self.sock = raw_sock self._tunnel() raw_sock = self.sock self.sock = self._context.wrap_socket( raw_sock, server_hostname=self.host, ) class ForcedIPHTTPHandler(urllib.request.HTTPHandler): """urllib handler that creates ForcedIPHTTPConnection.""" def __init__(self, *, ip_chooser: IpChooser): super().__init__() self._ip_chooser = ip_chooser def http_open(self, req) -> http.client.HTTPResponse: def factory(host, **kwargs): return ForcedIPHTTPConnection( host, ip_chooser=self._ip_chooser, timeout=kwargs.get("timeout"), ) return self.do_open(factory, req) class ForcedIPHTTPSHandler(urllib.request.HTTPSHandler): """urllib handler that creates ForcedIPHTTPSConnection.""" def __init__(self, *, ip_chooser: IpChooser, context: "ssl.SSLContext"): super().__init__(context=context) self._ip_chooser = ip_chooser self._context = context def https_open(self, req) -> http.client.HTTPResponse: def factory(host, **kwargs): return ForcedIPHTTPSConnection( host, ip_chooser=self._ip_chooser, context=self._context, timeout=kwargs.get("timeout"), ) return self.do_open(factory, req) class UrlTransport: """Single entrypoint for opening urllib requests. If *ip_chooser* is provided, the transport will connect to the selected IP address, while keeping correct Host/SNI/cert validation for the original hostname. If *ip_chooser* is not provided, it behaves like plain urllib. """ def __init__( self, *, ip_chooser: Optional[IpChooser] = None, ssl_context: Optional["ssl.SSLContext"] = None, use_proxies: bool = True, ): import ssl as _ssl self._ssl_context = ssl_context or _ssl.create_default_context() handlers: list = [] if not use_proxies: # empty mapping disables urllib's default env-based proxy handlers.append(urllib.request.ProxyHandler({})) if ip_chooser is not None: handlers += [ ForcedIPHTTPHandler(ip_chooser=ip_chooser), ForcedIPHTTPSHandler( ip_chooser=ip_chooser, context=self._ssl_context, ), ] self._opener = urllib.request.build_opener(*handlers) def open( self, req: urllib.request.Request, *, timeout: Optional[float] = None, ) -> http.client.HTTPResponse: if timeout is None: return self._opener.open(req) return self._opener.open(req, timeout=timeout) defence360agent/utils/parsers.py0000644000000000000000000002714700000000000013752 0ustar import argparse import ipaddress import sys from functools import lru_cache, partial from itertools import chain from typing import Any, Dict, Iterable, Iterator, Mapping, Tuple from defence360agent.application import app from defence360agent.contracts.config import Core as Config from defence360agent.rpc_tools.utils import prepare_schema from defence360agent.simple_rpc import RpcClient from defence360agent.utils.cli import EXITCODE_NOT_FOUND class SchemaToArgparse: # NOTE: 'default' is a normalization rule, 'required' is a validation rule OptionType = Iterator[Tuple[str, Any]] def __init__(self, argument, options): self._argument: str = argument self._allowed: Iterable = options.get("allowed") self._default: Any = options.get("default") self._envvar: str = options.get("envvar", False) self._help: str = options.get("help") self._positional: bool = options.get("positional", False) self._rename: str = options.get("rename") self._required: bool = options.get("required", False) self._type: str = options.get("type") @property def argname(self) -> str: if self._positional: return self._argument return "--" + self._argument.replace("_", "-") @property def options(self): argparse_options = dict( chain( self.choices(), self.default(), self.help(), self.metavar(), self.nargs(), self.required(), ), ) return argparse_options def nargs(self) -> OptionType: option = "nargs" if self._type == "list": # FIXME: all positional arguments are not required # to support `rename` if not self._positional and self._required and not self._envvar: yield option, "+" else: yield option, "*" elif self._positional and (self._envvar or self._default is None): yield option, "?" def choices(self) -> OptionType: yield "choices", self._allowed def help(self) -> OptionType: yield "help", self._help def metavar(self) -> OptionType: option = "metavar" if self._rename: yield option, self._rename.upper() elif self._type == "list": yield option, self._argument.upper() def default(self) -> OptionType: if ( self._default is not None and not self._envvar and (self._type == "list" or not self._positional) ): yield "default", self._default def required(self): if ( self._required and self._type != "list" and not self._envvar # 'required' is an invalid argument for positionals and not self._positional ): yield "required", True def schema_to_argparse(parser, argument, options): if options.get("type") == "boolean": required = options.get("required") and not options.get("envvar", False) bool_parser = parser.add_mutually_exclusive_group(required=required) bool_parser.add_argument( "--" + argument.replace("_", "-"), dest=argument, action="store_true", ) bool_parser.add_argument( "--no-" + argument.replace("_", "-"), dest=argument, action="store_false", ) bool_parser.set_defaults(**{argument: options.get("default")}) else: converter = SchemaToArgparse(argument, options) parser.add_argument(converter.argname, **converter.options) class EnvParser: @staticmethod def format_help(envvar_parameter_options: Mapping): if not envvar_parameter_options: return "" def format_arg(options): if "help" in options: return f"{options['envvar']}\t\t{options['help']}" return options["envvar"] return "\nenvironment variables: \n {}".format( "\n ".join( format_arg(options) for options in envvar_parameter_options.values() ) ) @staticmethod def _validate(envvar, value, options): if "isascii" in options: try: value.encode("ascii") except UnicodeEncodeError: return ( f"error: {envvar}={value} must only contain ascii symbols", ) return None @classmethod def parse( cls, environ: Mapping, command, envvar_parameter_options, exclude: Iterable[str], ) -> Dict[str, str]: kwargs = {} for parameter, options in envvar_parameter_options.items(): if parameter in exclude: continue envvar_name = options["envvar"] try: value = kwargs[parameter] = environ[envvar_name] except KeyError: if "default" in options: kwargs[parameter] = options["default"] continue if not options.get("required"): continue msg = cls._format_error( command, envvar_parameter_options, "error: environment variable {} is not defined".format( envvar_name ), ) print(msg, file=sys.stderr) sys.exit(EXITCODE_NOT_FOUND) else: if err := cls._validate(envvar_name, value, options): msg = cls._format_error( command, envvar_parameter_options, err ) print(msg, file=sys.stderr) sys.exit(EXITCODE_NOT_FOUND) return kwargs @classmethod def _format_error(cls, command, envvar_parameter_options, msg): return "{command}:\n{help}\n\n{message}".format( command=" ".join(command), help=cls.format_help(envvar_parameter_options), message=msg, ) def is_valid_ipv4_addr(addr): try: ipaddress.IPv4Address(addr) except ipaddress.AddressValueError: return False return True def _filter_user(schema, user): for key, values in schema.items(): if user in values.get("cli", {}).get("users", []): yield key, values def rpc_endpoint(command, require_rpc, **params): return RpcClient(require_svc_is_running=require_rpc).cmd(*command)( **params ) def generate_endpoint_params(arg_parser_namespace, arguments): kwargs = {} for argument in arguments: arg_parser_argument = argument.replace("-", "_") value = getattr(arg_parser_namespace, arg_parser_argument, None) if value is not None: kwargs[argument] = value return kwargs def apply_parser(subparsers, schema): _subparsers = {} commands = sorted(schema.keys()) for methods in commands: values = schema[methods] assert isinstance(methods, (tuple, list)) parser = None # generate subparsers subparser = subparsers for i, command in enumerate(methods): # last element if i == len(methods) - 1: parser = subparser.add_parser( name=command, help=values.get("help"), formatter_class=argparse.RawDescriptionHelpFormatter, ) if any( (c != methods and methods == c[: len(methods)]) for c in commands ): _subparsers[methods] = parser.add_subparsers( help="Available commands" ) else: # Need to reuse created subparsers for sub-commands, otherwise # they will be overwritten. # # Example: # For both of the commands: # * malware on-demand queue put # * malware on-demand queue remove # only one subparser is created. We should add `queue` # subparser only once in order to keep both `put` and `remove`. hashable = tuple(methods[: i + 1]) exists_subparser = _subparsers.get(hashable) if not exists_subparser: subparser = _subparsers[hashable] = subparser.add_parser( name=command, help=values.get("help"), ).add_subparsers(help="Available commands", required=True) else: subparser = exists_subparser assert parser, "parser is not defined" # generate arguments envvar_parameter_options = {} for argument, options in values.get("schema", {}).items(): if "envvar" in options: envvar_parameter_options[argument] = options if options.get("envvar_only", False): continue if "rename" in options: options.update(**values["schema"][options["rename"]]) options["required"] = False options["positional"] = False schema_to_argparse(parser, argument, options) parser.epilog = EnvParser.format_help(envvar_parameter_options) parser.add_argument( "--json", action="store_true", help="return data in JSON format" ) parser.add_argument("--verbose", "-v", action="count") require_rpc = values.get("cli", {}).get("require_rpc", "running") parser.set_defaults( # Initializing `RpcClient` here for each command will # inevitably lead to the `ServiceStateError`, # because some endpoints require the agent to be stopped and # some require it to be running. So we use `partial` to # defer initialization until the command is selected. endpoint=partial(rpc_endpoint, methods, require_rpc), generate_endpoint_params=partial( generate_endpoint_params, arguments=values.get("schema", {}).keys(), ), envvar_parameter_options=envvar_parameter_options, command=methods, ) def _apply_subparsers(subparsers, user): schema = dict(_filter_user(prepare_schema(app.SCHEMA_PATHS), user)) apply_parser(subparsers, schema) @lru_cache(maxsize=1) def create_cli_parser(): parser = argparse.ArgumentParser(description="CLI for %s." % Config.NAME) parser.add_argument("--log-config", help="logging config filename") parser.add_argument( "--console-log-level", choices=["ERROR", "WARNING", "INFO", "DEBUG"], help="Level of logging input to the console", ) parser.add_argument( "--remote-addr", type=lambda ip: ip if is_valid_ipv4_addr(ip) else None, help="Client's IP address for adding it to the whitelist", ) subparsers = parser.add_subparsers(help="Available commands") _apply_subparsers(subparsers, "root") _apply_completions_parser(subparsers) return parser def _apply_completions_parser(subparsers): from defence360agent.utils.completions import SUPPORTED_SHELLS completions_parser = subparsers.add_parser( "completions", help="Generate shell auto-completion scripts", ) completions_parser.add_argument( "shell", choices=SUPPORTED_SHELLS, help="Shell to generate completions for", ) completions_parser.set_defaults(completions_command=True) defence360agent/utils/resource_limits.py0000644000000000000000000001204600000000000015473 0ustar import asyncio import logging from contextlib import suppress from enum import Enum from os import fsdecode from pathlib import Path from typing import List, Optional, Tuple from defence360agent.utils import OsReleaseInfo logger = logging.getLogger(__name__) RUN_WITH_INTENSITY = "/usr/libexec/run-with-intensity" LVECTL_BIN_PATH = Path("/usr/sbin/lvectl") PROC_LVE_LIST_PATH = Path("/proc/lve/list") PROC_PATH = Path("/proc") CGROUP_PATH = Path("/sys/fs/cgroup") # cgroup v1 reports "no limit" as a page counter near its maximum _CGROUP_V1_NO_LIMIT = 1 << 50 class LimitsMethod(Enum): NICE = "nice" LVE = "lve" CGROUPS = "cgroups" async def get_current_method() -> LimitsMethod: """Returns limit method, used in run-with-intensity tool.""" proc = await asyncio.create_subprocess_exec( RUN_WITH_INTENSITY, "show", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await proc.communicate() stdout = fsdecode(stdout).strip() if stdout == "nice": return LimitsMethod.NICE if stdout == "lve": return LimitsMethod.LVE if stdout == "cgroups": return LimitsMethod.CGROUPS raise LookupError( "Parsing of used limitation method failed\nstdout: {}\nstderr: {}" .format(stdout, fsdecode(stderr).strip()) ) async def create_subprocess( cmd: List[str], key: str, intensity_cpu: int, intensity_io: int, **subprocess_kwargs ) -> asyncio.subprocess.Process: """ Creates asyncio.Process with limited resources (cpu & io), using run-with-intensity tool. :param cmd: command to execute :param intensity_cpu: cpu intensity limit :param intensity_io: io intensity limit :param subprocess_kwargs: keyword arguments for create_subprocess_exec func :return: executed Process """ limits_cmd = [ RUN_WITH_INTENSITY, "run", "--intensity-cpu", str(intensity_cpu), "--intensity-io", str(intensity_io), ] limits_cmd.extend(["--key", key]) return await asyncio.create_subprocess_exec( *(limits_cmd + cmd), **subprocess_kwargs ) def _status_field_kb(pid: int, field: str) -> Optional[int]: try: status = (PROC_PATH / str(pid) / "status").read_text() except OSError: return None for line in status.splitlines(): name, _, value = line.partition(":") if name == field: with suppress(IndexError, ValueError): return int(value.split()[0]) return None def _child_pids(pid: int) -> List[int]: children: List[int] = [] try: threads = sorted((PROC_PATH / str(pid) / "task").iterdir()) except OSError: return children for thread in threads: try: listed = (thread / "children").read_text() except OSError: continue with suppress(ValueError): children.extend(int(child) for child in listed.split()) return children def _process_tree(pid: int) -> List[int]: tree, pending = [], [pid] while pending: current = pending.pop() tree.append(current) pending.extend(_child_pids(current)) return tree def peak_rss_kb(pid: int) -> Optional[int]: """Peak RSS of the process and its descendants, summed, in kB.""" total = None for process in _process_tree(pid): peak = _status_field_kb(process, "VmHWM") if peak is not None: total = (total or 0) + peak return total def _cgroup_limit_bytes(path: Path) -> Optional[int]: try: value = path.read_text().strip() except OSError: return None if value == "max": return None with suppress(ValueError): limit = int(value) if limit < _CGROUP_V1_NO_LIMIT: return limit return None def memory_bound_kb(pid: int) -> Optional[Tuple[int, str]]: """The cgroup memory limit the process runs under, in kB, and its source.""" try: cgroups = (PROC_PATH / str(pid) / "cgroup").read_text() except OSError: return None for line in cgroups.splitlines(): _, _, rest = line.partition(":") controllers, _, cgroup = rest.partition(":") relative = cgroup.lstrip("/") if not controllers: limit = _cgroup_limit_bytes(CGROUP_PATH / relative / "memory.max") source = "cgroup v2" elif "memory" in controllers.split(","): limit = _cgroup_limit_bytes( CGROUP_PATH / "memory" / relative / "memory.limit_in_bytes" ) source = "cgroup v1" else: continue if limit is not None: return limit // 1024, source return None def is_lve_active() -> bool: """Checks that LVE-utils is active resource limiter.""" # to avoid possible errors such as DEF-11941 # make sure that OS is CL return PROC_LVE_LIST_PATH.exists() and OsReleaseInfo.is_cloudlinux() def has_lvectl() -> bool: """Checks that LVE-utils is installed.""" return LVECTL_BIN_PATH.exists() defence360agent/utils/safe_fileops.py0000644000000000000000000002352000000000000014721 0ustar import asyncio import atexit import functools import logging import os import pathlib import pwd import shutil import stat from concurrent.futures import ProcessPoolExecutor from contextlib import contextmanager, suppress from itertools import chain from typing import Set, Tuple, Union from defence360agent import utils R_FLAGS = os.O_RDONLY W_FLAGS = os.O_TRUNC | os.O_CREAT | os.O_WRONLY logger = logging.getLogger(__name__) # Track active ProcessPoolExecutors so they can be cleaned up during shutdown. # Each call to drop() permanently changes the worker process identity, so we # must use a fresh executor per call. We track them to prevent orphaned worker # processes from blocking agent shutdown (causing systemd SIGKILL). _active_pools: Set[ProcessPoolExecutor] = set() async def _run_in_fresh_executor(loop: asyncio.AbstractEventLoop, *args): pool = ProcessPoolExecutor(max_workers=1) _active_pools.add(pool) try: return await loop.run_in_executor(pool, *args) finally: try: pool.shutdown(wait=False) finally: _active_pools.discard(pool) def shutdown_process_pools() -> None: """Shutdown all tracked ProcessPoolExecutors. Should be called during agent shutdown to ensure clean process termination. """ for pool in list(_active_pools): try: pool.shutdown(wait=False, cancel_futures=True) except Exception as e: logger.warning("Error shutting down ProcessPoolExecutor: %s", e) _active_pools.clear() # Register cleanup at exit as a fallback atexit.register(shutdown_process_pools) def drop(fun, uid, gid, *args): os.setgroups([]) os.setgid(gid) os.setuid(uid) return fun(*args) class UnsafeFileOperation(Exception): pass def ensure_regular_file(path: str) -> None: """Verify path is a regular file; remove and raise FileNotFoundError if not. Uses os.lstat() to avoid following symlinks. If the file is a FIFO, symlink, socket, device, etc., it is deleted so the caller can recreate it as a regular file. """ st = os.lstat(path) # raises FileNotFoundError if missing if not stat.S_ISREG(st.st_mode): logger.warning( "Identity file %s is not a regular file (mode=%s), removing", path, stat.filemode(st.st_mode), ) os.unlink(path) raise FileNotFoundError(f"Removed non-regular identity file: {path}") def check_non_admin_file(file): st = os.stat(str(file)) if st.st_uid < utils.get_min_uid(): raise UnsafeFileOperation( "The file belongs to admin user: " + str(file) ) return True def safe(missing_ok=False): def _safe(fun): @functools.wraps(fun) async def wrapper(filename, *args, loop=None): if not os.path.exists(filename) and not missing_ok: raise FileNotFoundError( "No such file or directory: " + filename ) path = pathlib.Path(filename) paths = chain(reversed(path.parents), [path]) if missing_ok: paths = reversed(path.parents) for p in paths: st = os.stat(str(p)) if st.st_uid != 0 and st.st_gid != 0: uid, gid = st.st_uid, st.st_gid break else: raise UnsafeFileOperation( "Unsafe file operation under root: " + str(path) ) loop = loop or asyncio.get_event_loop() return await _run_in_fresh_executor( loop, drop, fun, uid, gid, filename, *args, ) return wrapper return _safe def _touch(filename: str): pathlib.Path(filename).touch() def _write_text(filename: str, data: str): pathlib.Path(filename).write_text(data) # This is the only way to make _write_text and _touch pickable. # If we use decorator syntax instead - it's impossible # to use them in multiprocessing async def write_text(filename: str, data: str): return await safe(missing_ok=True)(_write_text)(filename, data) async def touch(filename: str): return await safe(missing_ok=True)(_touch)(filename) chmod = safe(os.chmod) unlink = safe(os.unlink) @contextmanager def safe_open_file(filename, mode, user, respect_homedir=True): if "w" in mode: raise UnsafeFileOperation("'w' mode is not permitted") with open(filename, mode) as f: st = os.fstat(f.fileno()) passwd = pwd.getpwnam(user) real_path = os.readlink(f"/proc/self/fd/{f.fileno()}") filename_str = str(filename) # Checking if no symlinks along the pathway... # Unfortunately, that is going to fail for hosters that mapped # /home dir to be e.g. # /home -> /mnt/sdb1/home if (filename_str != real_path) or (st.st_uid != passwd.pw_uid): raise UnsafeFileOperation(f"Unable to safely read {filename_str}") if ( respect_homedir and pathlib.Path(passwd.pw_dir) not in pathlib.Path(filename_str).parents ): raise UnsafeFileOperation( f"Unable to safely read {filename_str}. " "File is not in user homedir" ) yield f @contextmanager def open_fd(*args, **kwargs): """ Context manager which wraps os.open and close file descriptor at the end :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open """ fd = os.open(*args, **kwargs) try: yield fd finally: with suppress(OSError): # fd is already closed os.close(fd) @contextmanager def opendir_fd(name: str, *args, **kwargs): """ Context manager to get a directory file descriptor It also checks if a directory doesn't contain a symlink in the path :param name: full directory name :param args: positional arguments for os.open :param kwargs: keyword arguments for os.open """ with open_fd(name, *args, flags=os.O_DIRECTORY, **kwargs) as dir_fd: real = os.readlink("/proc/self/fd/{}".format(dir_fd)) if name != real: raise UnsafeFileOperation("Operations on symlinks are prohibited") yield dir_fd @contextmanager def open_fobj(f: Union[str, int], dir_fd=None, flags=0, mode=None): """ Context manager to open file object from file name or from file descriptor File object extended with 'st' attribute that contains os.stat_result of the opened file :param f: file name or file descriptor to open :param dir_fd: directory descriptor, ignored if 'f' is a file descriptor :param flags: flags for os.open, ignored if 'f' is a file descriptor :param mode: mode for built-in open """ st = None if isinstance(f, str): # safe_* == False with suppress(OSError): # make a file readable/writable by an owner st = os.stat(f, dir_fd=dir_fd) os.chmod( f, mode=st.st_mode | stat.S_IRUSR | stat.S_IWUSR, dir_fd=dir_fd ) f = os.open(f, flags=flags, dir_fd=dir_fd) with open(f, mode=mode) as fo: fo.st = st or os.stat(f) try: yield fo finally: if st: # revert file permissions with suppress(OSError): os.chmod(f, mode=st.st_mode) @contextmanager def safe_tuple(name: str, dir_fd: int, flags: int, is_safe: bool): """ If is_safe flag is True, open file descriptor using name and dir_fd If is_safe is False, return name and dir_fd as is """ if is_safe: with open_fd(name, dir_fd=dir_fd, flags=flags) as fd: yield fd, None else: yield name, dir_fd def _move( src: Union[Tuple[str, int], Tuple[int, None]], dst: Union[Tuple[str, int], Tuple[int, None]], src_unlink, dst_overwrite, racecall, ): src_f, src_dir_fd = src dst_f, dst_dir_fd = dst w_flags = W_FLAGS | (0 if dst_overwrite else os.O_EXCL) with open_fobj( src_f, dir_fd=src_dir_fd, flags=R_FLAGS, mode="rb" ) as src_fo: with open_fobj( dst_f, dir_fd=dst_dir_fd, flags=w_flags, mode="wb" ) as dst_fo: if racecall: racecall[0]() shutil.copyfileobj(src_fo, dst_fo) if isinstance(dst_f, str): # safe_dst == False os.chmod(dst_fo.fileno(), mode=src_fo.st.st_mode) if src_unlink and isinstance(src_f, str): # safe_src == False if racecall: racecall[1]() os.unlink(src_f, dir_fd=src_dir_fd) async def safe_move( src: str, dst: str, safe_src=False, safe_dst=False, src_unlink=True, dst_overwrite=False, racecall=None, ): src_dir, src_name = os.path.split(src) dst_dir, dst_name = os.path.split(dst) with opendir_fd(src_dir) as src_dir_fd, opendir_fd( dst_dir ) as dst_dir_fd, safe_tuple( src_name, src_dir_fd, R_FLAGS, safe_src ) as src_tuple, safe_tuple( dst_name, dst_dir_fd, W_FLAGS, safe_dst ) as dst_tuple: src_st = os.stat(src_name, dir_fd=src_dir_fd) loop = asyncio.get_event_loop() await _run_in_fresh_executor( loop, drop, _move, src_st.st_uid, src_st.st_gid, src_tuple, dst_tuple, src_unlink, dst_overwrite, racecall, ) if src_unlink and safe_src: if racecall: racecall[1]() os.unlink(src_name, dir_fd=src_dir_fd) if safe_dst: os.chown(dst_name, src_st.st_uid, src_st.st_gid, dir_fd=dst_dir_fd) os.chmod(dst_name, src_st.st_mode, dir_fd=dst_dir_fd) defence360agent/utils/safe_sequence.py0000644000000000000000000000055300000000000015071 0ustar import os def path(p: str): """ Make safe sequence from path-like string Useful if p contains unprintable sequence If p is safe to be printed (e.g. via logger) return it as is If it can cause an exception, return bytes instead """ try: p.encode() except UnicodeEncodeError: return os.fsencode(p) return p defence360agent/utils/serialization.py0000644000000000000000000000455200000000000015143 0ustar """JSON persistence helpers for small agent state files (no pickle at runtime).""" import collections import functools import json import logging import os from asyncio import iscoroutinefunction from typing import Any, Callable, Union logger = logging.getLogger(__name__) def _to_jsonable(obj: Any) -> Any: if isinstance(obj, collections.deque): return [_to_jsonable(item) for item in obj] if isinstance(obj, dict): return {k: _to_jsonable(v) for k, v in obj.items()} if isinstance(obj, (list, tuple)): return [_to_jsonable(item) for item in obj] return obj def _dump(path, obj): """Atomically write ``obj`` to ``path`` as JSON.""" payload = json.dumps(_to_jsonable(obj)) tmp = "{}.tmp".format(path) with open(tmp, "w", encoding="utf-8") as w: w.write(payload) os.replace(tmp, path) def serialize_attr(*, path: str, attr: str): """Decorator: after the wrapped method runs, persist ``self.`` to ``path`` as JSON.""" def decorator(f): @functools.wraps(f) def wrapper(self, *args, **kwargs): result = f(self, *args, **kwargs) obj = getattr(self, attr) logger.debug("Write %r to %r", obj, path) _dump(path, obj) return result @functools.wraps(f) async def async_wrapper(self, *args, **kwargs): result = await f(self, *args, **kwargs) obj = getattr(self, attr) logger.debug("Write %r to %r", obj, path) _dump(path, obj) return result if iscoroutinefunction(f): return async_wrapper return wrapper return decorator def unserialize(*, path: str, fallback: Union[Callable, object] = None): """Restore an object from ``path`` (JSON); a top-level list becomes a deque to match the legacy queue API, and missing/unparseable input returns ``fallback`` (called if callable).""" try: with open(path, "r", encoding="utf-8") as r: obj = json.load(r) except FileNotFoundError: logger.warning("Can't find %s to unserialize", path) except Exception as e: logger.error("Unserialize failed with %r. Returning fallback", e) else: if isinstance(obj, list): return collections.deque(obj) return obj return fallback() if callable(fallback) else fallback defence360agent/utils/sshutil.py0000644000000000000000000003550600000000000013764 0ustar import asyncio import datetime import errno import pwd import re import stat import urllib.request import os from logging import getLogger from urllib.error import URLError from pathlib import Path from defence360agent.utils import BACKUP_EXTENSION, atomic_rewrite from defence360agent.utils.fd_ops import open_dir_no_symlinks logger = getLogger(__name__) ANALYST_PUB_KEY_URL = ( "https://repo.imunify360.cloudlinux.com/defense360/assisted-cleanup.pub" ) KEY_PATTERN = r"clsupport@sshbox\.cloudlinux\.com" SSH_CONFIG_PATH = Path("/etc/ssh/sshd_config") SSH_CONFIG_DIR = Path("/etc/ssh/sshd_config.d") # \Z (not $) — $ would accept a trailing newline. _USERNAME_RE = re.compile(r"^[a-z_][a-z0-9_-]{0,31}\Z") def _resolve_authorized_keys(username: str) -> Path: """Home dir via pwd.getpwnam, not /home/ concatenation, to block path traversal.""" if not isinstance(username, str) or not _USERNAME_RE.match(username): raise ValueError("invalid username: %r" % (username,)) if username == "root": return Path("/root/.ssh/authorized_keys") try: home = pwd.getpwnam(username).pw_dir except KeyError as e: raise ValueError("no such user: %r" % (username,)) from e # pwd.pw_dir is normally absolute, but panel-driven user creation can # leave it empty or relative; refuse rather than write under CWD. if not home or not os.path.isabs(home): raise ValueError( "non-absolute home directory for %r: %r" % (username, home) ) return Path(os.path.join(home, ".ssh", "authorized_keys")) # The support pub key is shared across every Imunify install, so a leaked # private counterpart would grant root on the whole fleet. Bound the blast # radius via restrict + expiry-time options on the authorized_keys line. DEFAULT_KEY_TTL_DAYS = 7 KEY_TTL_ENV_VAR = "IMUNIFY_ASSISTED_CLEANUP_KEY_TTL_DAYS" KEY_OPTIONS_BASE = "restrict,pty" async def get_ssh_port(): """ Detect SSH port from config and its overrides. Searches configs in reverse order to find the last override first. """ port = 22 # default port try: # Collect and sort config files config_files = [SSH_CONFIG_PATH] if SSH_CONFIG_DIR.exists(): config_files.extend(sorted(SSH_CONFIG_DIR.glob("*.conf"))) # Process files for config_file in reversed(config_files): try: for line in config_file.read_text().splitlines(): line = line.strip() if line.startswith("Port ") and not line.startswith("#"): try: # return first match # since we are searching backwards port = int(line.split()[1]) return port except (IndexError, ValueError): continue except IOError as e: logger.warning(f"Failed to read {config_file}: {e}") continue except Exception as e: logger.warning(f"Failed to get SSH port: {e}") finally: return port async def check_ssh_connection(port=22): """Test if port is actually an SSH port by checking the server banner""" try: reader, writer = await asyncio.open_connection("127.0.0.1", port) try: banner = await asyncio.wait_for(reader.readline(), timeout=5.0) banner = banner.decode("utf-8", errors="ignore").strip() if re.match(r"^SSH-[12]\.", banner): logger.info( f"Port {port} is confirmed as SSH (banner: {banner})" ) return True else: logger.warning( f"Port {port} is open but not SSH (got: {banner})" ) return False except asyncio.TimeoutError: logger.warning(f"Timeout waiting for SSH banner on port {port}") return False finally: writer.close() await writer.wait_closed() except (ConnectionRefusedError, OSError) as e: logger.warning(f"Failed to connect to port {port}: {e}") return False except Exception as e: logger.warning(f"Unexpected error checking SSH port {port}: {e}") return False def _key_ttl_days() -> int: """Read the assisted-cleanup key TTL from env, falling back to default.""" raw = os.environ.get(KEY_TTL_ENV_VAR, "") try: ttl = int(raw) if ttl > 0: return ttl except (TypeError, ValueError): pass return DEFAULT_KEY_TTL_DAYS def _expiry_timestamp(now: "datetime.datetime | None" = None) -> str: # Bare timestamp (no Z): Z requires OpenSSH >= 9.1; without it sshd # parses as local time per authorized_keys(5), so convert before format. base = now or datetime.datetime.now(datetime.timezone.utc) expiry = base.astimezone() + datetime.timedelta(days=_key_ttl_days()) return expiry.strftime("%Y%m%d%H%M") _OPENSSH_VERSION_RE = re.compile(r"OpenSSH_(\d+)\.(\d+)") async def _sshd_supports_expiry_time() -> bool: # expiry-time keyword exists since OpenSSH 7.7; older sshd (CL7) rejects # the whole line. Probe failure -> False so we fall back to restrict,pty. try: proc = await asyncio.create_subprocess_exec( "ssh", "-V", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=5) except (OSError, asyncio.TimeoutError) as e: logger.warning("ssh -V probe failed: %s", e) return False output = (stderr or b"").decode("utf-8", errors="ignore") or ( stdout or b"" ).decode("utf-8", errors="ignore") match = _OPENSSH_VERSION_RE.search(output) if not match: logger.warning( "ssh -V did not match OpenSSH version pattern: %r", output[:200] ) return False major, minor = int(match.group(1)), int(match.group(2)) return (major, minor) >= (7, 7) def build_authorized_key_line(pub_key: str, *, supports_expiry: bool) -> str: if supports_expiry: options = f'{KEY_OPTIONS_BASE},expiry-time="{_expiry_timestamp()}"' else: options = KEY_OPTIONS_BASE return f"{options} {pub_key.strip()}" def _target_uid_gid(username: str): """Resolve uid/gid for the target user, or (None, None) when not applicable. Returning ``(None, None)`` for root or unknown users lets ``atomic_rewrite`` skip its chown step and preserve the existing file's ownership. """ if username == "root": return None, None try: pw = pwd.getpwnam(username) except KeyError: logger.warning( "user %r not found; leaving authorized_keys ownership untouched", username, ) return None, None return pw.pw_uid, pw.pw_gid def _open_ssh_dir(home_fd, uid, gid, *, create): """O_NOFOLLOW fd for .ssh under *home_fd*; a symlinked .ssh raises.""" created = False if create: try: os.mkdir(".ssh", mode=0o700, dir_fd=home_fd) created = True except FileExistsError: pass ssh_fd = os.open( ".ssh", os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=home_fd, ) if created: try: if uid is not None and gid is not None: os.chown(ssh_fd, uid, gid) os.fchmod(ssh_fd, 0o700) except BaseException: os.close(ssh_fd) raise return ssh_fd async def install_pub_key(username="root"): # Idempotent: re-running rotates the expiry and replaces any legacy # (unguarded or older guarded) copy of the same key. try: try: auth_keys_path = _resolve_authorized_keys(username) except ValueError as e: logger.error("install_pub_key: %s", e) return False # If not running as root, fail if os.geteuid() != 0: logger.error("Function must be run as root") return False # Download the public key try: pub_key = ( urllib.request.urlopen(ANALYST_PUB_KEY_URL) .read() .decode() .strip() ) except URLError as e: logger.error(f"Failed to download public key: {e}") return False # A genuine key is single-line; an embedded newline would split into # a second, option-less authorized_keys entry that bypasses restrict. if "\n" in pub_key or "\r" in pub_key: logger.error("Downloaded public key spans multiple lines") return False guarded_line = build_authorized_key_line( pub_key, supports_expiry=await _sshd_supports_expiry_time(), ) uid, gid = _target_uid_gid(username) # Components above the user's home are root-controlled, so one # realpath is safe; everything below is opened with O_NOFOLLOW # and operated on dir_fd-relative, leaving no symlink-swap window. home = os.path.realpath(auth_keys_path.parent.parent) try: home_fd = open_dir_no_symlinks(home) except OSError as e: logger.error(f"Cannot open home directory {home}: {e}") return False try: try: ssh_fd = _open_ssh_dir(home_fd, uid, gid, create=True) except OSError as e: logger.error( f"Failed to prepare directory {auth_keys_path.parent}: {e}" ) return False try: permissions = None try: keys_fd = os.open( "authorized_keys", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=ssh_fd, ) except FileNotFoundError: existing = "" permissions = 0o600 except OSError as e: if e.errno != errno.ELOOP: raise logger.warning("Replacing symlinked %s", auth_keys_path) existing = "" permissions = 0o600 else: with os.fdopen(keys_fd, "r") as f: existing = f.read() # Strip any prior copy of the support key (legacy # unguarded or older guarded line) so re-running rotates # options + expiry instead of stacking duplicates. stripped = re.sub( r".*" + KEY_PATTERN + r".*\n?", "", existing, ) new_content = stripped if new_content and not new_content.endswith("\n"): new_content += "\n" new_content += guarded_line + "\n" atomic_rewrite( "authorized_keys", new_content, backup=False, uid=uid, gid=gid, permissions=permissions, dir_fd=ssh_fd, ) finally: os.close(ssh_fd) finally: os.close(home_fd) logger.info( "Installed assisted-cleanup key for user %s (%s)", username, guarded_line.split(" ", 1)[0], ) return True except Exception as e: logger.error(f"Failed to install public key: {e}") return False def remove_pub_key(username="root") -> bool: """Remove analyst public key for the specified user This function removes the analyst's public key that was previously installed using the install_pub_key function. returns: True if key was successfully removed, False otherwise. """ try: try: auth_keys_path = _resolve_authorized_keys(username) except ValueError as e: logger.error("remove_pub_key: %s", e) return False uid, gid = _target_uid_gid(username) home = os.path.realpath(auth_keys_path.parent.parent) try: home_fd = open_dir_no_symlinks(home) except OSError as e: logger.warning(f"Cannot open home directory {home}: {e}") return False try: try: ssh_fd = _open_ssh_dir(home_fd, uid, gid, create=False) except OSError as e: logger.warning( f"Cannot open directory {auth_keys_path.parent}: {e}" ) return False try: try: keys_fd = os.open( "authorized_keys", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=ssh_fd, ) except OSError as e: logger.warning(f"Cannot open {auth_keys_path}: {e}") return False with os.fdopen(keys_fd, "r") as f: permissions = stat.S_IMODE(os.fstat(f.fileno()).st_mode) content = f.read() if not re.search(KEY_PATTERN, content): logger.info( f"Analyst public key not found in {auth_keys_path}" ) return False # Remove the key (including the line it's on) new_content = re.sub( r".*" + KEY_PATTERN + r".*\n?", "", content ) if not new_content.strip(): logger.info( f"File {auth_keys_path} will be empty after removal" ) # atomic_rewrite's own backup mode is path-based and thus # symlink-unsafe; write the backup through the same pinned # descriptor instead. atomic_rewrite( "authorized_keys" + BACKUP_EXTENSION, content, backup=False, uid=uid, gid=gid, permissions=permissions, dir_fd=ssh_fd, ) atomic_rewrite( "authorized_keys", new_content, backup=False, uid=uid, gid=gid, dir_fd=ssh_fd, ) logger.info( "Successfully removed analyst public key from" f" {auth_keys_path}" ) return True finally: os.close(ssh_fd) finally: os.close(home_fd) except Exception as e: logger.error(f"Failed to remove public key: {e}") return False defence360agent/utils/subprocess.py0000644000000000000000000000304200000000000014447 0ustar """General utilities for working with subprocesses.""" import signal import subprocess from subprocess import PIPE # noqa: F401 __all__ = ["PIPE", "CalledProcessError", "check_output"] class CalledProcessError(subprocess.CalledProcessError): """Add stdout,stderr to str representation""" def __str__(self): if self.returncode and self.returncode < 0: try: return "Command '%s' died with %r.\nStdout: %s\nStderr: %s" % ( self.cmd, signal.Signals( -self.returncode ), # noqa E501 pylint: disable=E1101 self.stdout, self.stderr, ) except ValueError: return ( "Command '%s' died with unknown signal %d." "\nStdout: %s\nStderr: %s" % (self.cmd, -self.returncode, self.stdout, self.stderr) ) else: return ( "Command '%s' returned non-zero exit status %d." "\nStdout: %s\nStderr: %s" % (self.cmd, self.returncode, self.stdout, self.stderr) ) def check_output(*args, **kwargs): """A wrapper for stdlib subprocess.check_output. Include stdout/stderr in error message. """ try: return subprocess.check_output(*args, **kwargs) except subprocess.CalledProcessError as e: raise CalledProcessError( e.returncode, e.cmd, e.stdout, e.stderr ) from None defence360agent/utils/support.py0000644000000000000000000001232100000000000013773 0ustar import asyncio import io import json import socket import urllib.parse import urllib.request from functools import partial from logging import getLogger from pathlib import Path from defence360agent.contracts.config import ANTIVIRUS_MODE logger = getLogger(__name__) class ZendeskAPIError(Exception): def __init__(self, error, description, details): self.error = error self.description = description self.details = details super().__init__(description) _API_URL_TMPL = "https://cloudlinux.zendesk.com/api/v2/{}" _HC_URL_TMPL = "https://cloudlinux.zendesk.com/hc/requests/{}" # Identifiers for custom fields in API _PRODUCT_ID = 33267569 _DOCTOR_ID = 43297669 _CLN_ID = 43148369 _PRIVACY_POLICY_ID = 12355021509788 async def send_request( sender_email, subject, description, doctor_key=None, cln=None, attachments=None, ): """ Send request to support of Imunify360 via Zendesk API """ # Uploading attachments to Zendesk upload_token = await _upload_attachments(attachments) # Creating comment object: setting description and attaching # uploads token comment = dict(body=description) if upload_token is not None: comment["uploads"] = [upload_token] # Author of request requester = dict(name=sender_email, email=sender_email) # Custom fields for support convenience custom_fields = [ { "id": _PRODUCT_ID, "value": "pr_imunify_av" if ANTIVIRUS_MODE else "pr_im360", }, {"id": _PRIVACY_POLICY_ID, "value": True}, ] if doctor_key: custom_fields.append({"id": _DOCTOR_ID, "value": doctor_key}) if cln: custom_fields.append({"id": _CLN_ID, "value": cln}) # Ready request request = dict( requester=requester, subject=subject, comment=comment, custom_fields=custom_fields, ) return await _post_support_request(request) def decode_as_json(response): return json.load( io.TextIOWrapper( response, encoding=response.headers.get_content_charset("utf-8"), ) ) def parse_params(params, url): p = urllib.parse.urlparse(url) query = p.query if query: query += "&" query += urllib.parse.urlencode(params) url = urllib.parse.urlunparse( (p.scheme, p.netloc, p.path, p.params, query, p.fragment) ) return url def _post_data(url, data: bytes, headers, *, params=None, timeout=None): """HTTP POST *data* to *url* with given *headers*. Add query *params* to the *url* if given. Return (http_status, decoded_json_response) tuple. """ if params: # add params to the url url = parse_params(params, url) try: with urllib.request.urlopen( urllib.request.Request(url, data=data, headers=headers), timeout=timeout, ) as response: return response.code, decode_as_json(response) # http status except socket.timeout: raise TimeoutError except OSError as e: if not hasattr(e, "code"): raise # HTTPError return e.code, (decode_as_json(e) if e.fp is not None else {}) async def _post_support_request(request): """Return url of the support request or None if request is suspended, because of we not able to obtain the id of the ticket if it suspended. """ url = _API_URL_TMPL.format("requests.json") headers = {"Content-Type": "application/json"} data = json.dumps(dict(request=request), sort_keys=True).encode("ascii") loop = asyncio.get_event_loop() status, result = await loop.run_in_executor( None, _post_data, url, data, headers ) if status == 201: request_data = result.get("request") if request_data: return _HC_URL_TMPL.format(request_data["id"]) elif "suspended_ticket" in result.keys(): return None else: raise ZendeskAPIError( "Response error", "UNKNOWN ERROR", "{!r}".format(result) ) else: raise ZendeskAPIError( result.get("error", "UNKNOWN ERROR"), result.get("description"), result.get("details", {}), ) async def _upload_attachments(attachments): # Uploading attachments to Zendesk upload_token = None if attachments is None: return upload_token loop = asyncio.get_event_loop() for attachment in attachments: path = Path(attachment) params = {"filename": path.name} if upload_token is not None: params["token"] = upload_token status, result = await loop.run_in_executor( None, partial( _post_data, _API_URL_TMPL.format("uploads.json"), data=path.read_bytes(), headers={"Content-Type": "application/binary"}, params=params, ), ) if status != 201: logger.warning( "Failed to upload file %s to Zendesk: %s", attachment, result["error"], ) continue if upload_token is None: upload_token = result["upload"]["token"] return upload_token defence360agent/utils/threads.py0000644000000000000000000000175500000000000013722 0ustar """High-level support for working with threads in asyncio Modified from Python 3.10 stdlib https://github.com/python/cpython/blob/b11a951f16f0603d98de24fee5c023df83ea552c/Lib/asyncio/threads.py (the license GPL-compatible but doesn't require to open-source either). """ import functools import contextvars from asyncio import events __all__ = ("to_thread",) async def to_thread(func, /, *args, **kwargs): """Asynchronously run function *func* in a separate thread. Any *args and **kwargs supplied for this function are directly passed to *func*. Also, the current :class:`contextvars.Context` is propogated, allowing context variables from the main thread to be accessed in the separate thread. Return a coroutine that can be awaited to get the eventual result of *func* """ loop = events.get_running_loop() ctx = contextvars.copy_context() func_call = functools.partial(ctx.run, func, *args, **kwargs) return await loop.run_in_executor(None, func_call) defence360agent/utils/validate.py0000644000000000000000000001042700000000000014055 0ustar from enum import Enum from ipaddress import ( IPV4LENGTH, IPV6LENGTH, AddressValueError, IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_address, ip_network, ) from typing import Literal, Optional, Union IPVersion = Literal["ipv4", "ipv6"] class LocalhostIP(str, Enum): ipv4 = "127.0.0.1" ipv6 = "::1" def __str__(self): return self.value class NumericIPVersion(int, Enum): """Example: (IPListRecord.version==NumericIPVersion[ip_version])""" ipv4 = 4 ipv6 = 6 def __str__(self): return str(self.value) @classmethod def from_ip_version( cls, ip_version: Optional[IPVersion] ) -> Optional["NumericIPVersion"]: if ip_version is None: return None return cls.ipv4 if ip_version == IP.V4 else cls.ipv6 def is_valid_ipv4_addr(addr): return IP.is_valid_ipv4_addr(addr) def is_valid_ipv4_network(addr, strict=False): return IP.is_valid_ipv4_network(addr, strict) class IP: V4: IPVersion = "ipv4" V6: IPVersion = "ipv6" @classmethod def check_ip_ver(cls, version): return any(version == ver for ver in [cls.V4, cls.V6]) @classmethod def is_valid_ip(cls, addr): try: ip_address(addr) except ValueError: return False return True @classmethod def is_valid_ip_network(cls, *args, **kwargs): return cls.is_valid_ipv4_network( *args, **kwargs ) or cls.is_valid_ipv6_network(*args, **kwargs) @classmethod def is_valid_ipv4_addr(cls, addr): try: IPv4Address(addr) except AddressValueError: return False return True @classmethod def is_valid_ipv6_addr(cls, addr): try: IPv6Address(addr) except AddressValueError: return False return True @classmethod def is_valid_ipv4_network( cls, addr: Union[str, IPv4Network, IPv6Network], strict=False ): try: ip = IPv4Network(addr) except ValueError: return False if strict: # IPV4LENGTH - netmask for host return ip.prefixlen != IPV4LENGTH return True @classmethod def is_valid_ipv6_network( cls, addr: Union[str, IPv4Network, IPv6Network], strict=False ): try: ip = IPv6Network(addr) except ValueError: return False if strict: # IPV6LENGTH - netmask for host return ip.prefixlen != IPV6LENGTH return True @classmethod def type_of(cls, addr): if cls.is_valid_ipv4_network(addr): return IP.V4 elif cls.is_valid_ipv6_network(addr): return IP.V6 raise ValueError("Invalid ip address") @classmethod def convert_to_ipv6_network(cls, ip, mask="/64"): """Conver ipv6 addr to ipv6 network with mask :param str ip: ip for converting :param str mask: ip network mask """ network = IPv6Network(ip + mask, strict=False) return str(network) @staticmethod def adopt_to_ipvX_network( ip_arg: Union[str, IPv4Address, IPv4Network, IPv6Address, IPv6Network] ) -> Union[IPv4Network, IPv6Network]: """ Eliminate str from the Union :raise ValueError: if cannot convert ip_arg str to ip network """ if isinstance(ip_arg, (IPv4Network, IPv6Network)): return ip_arg elif isinstance(ip_arg, (IPv4Address, IPv6Address)): prefixlen = IPV4LENGTH if ip_arg.version == 4 else IPV6LENGTH return ip_network((int(ip_arg), prefixlen)) return ip_network(ip_arg) @classmethod def ip_net_to_string(cls, net: Union[IPv4Network, IPv6Network]) -> str: """ IPv4Network('192.168.1.1/32') -> '192.168.1.1' IPv4Network('192.168.1.0/24') -> '192.168.1.0/24' """ if not int(net.hostmask): return str(net.network_address) return str(net) @classmethod def ipv6_to_64network( cls, ip: Union[IPv4Address, IPv6Address, str] ) -> Union[IPv4Address, IPv6Network]: if isinstance(ip, IPv6Address): return IPv6Network(cls.convert_to_ipv6_network(str(ip))) return ip defence360agent/utils/whmcs.py0000644000000000000000000001715000000000000013405 0ustar import json import os import urllib.parse import defence360agent.subsys.panels.hosting_panel as hp from logging import getLogger from defence360agent.contracts import config from defence360agent.utils.config import update_config from defence360agent.myimunify.model import update_users_protection, MyImunify from defence360agent.utils.wordpress_mu_plugin import ( MU_PLUGIN_INSTALLATION, ADVICE_EMAIL_NOTIFICATION, WordPressMuPlugin, ) logger = getLogger(__name__) MU_PLUGIN_KEYS = [MU_PLUGIN_INSTALLATION, ADVICE_EMAIL_NOTIFICATION] class WhmcsConf: """ read/write data passed by whmcs Internal use, for commands called from whcms only it saves ALL data came from whcms w/o any validation deliberately in order to simplify compatability with current installed whmcs plugin """ path = "/var/imunify360/whmcs_data.json" def read(self): if not os.path.exists(self.path): return {} try: with open(self.path, "r") as f: raw_data = f.read() except IOError as e: logger.error("Failed to read whmcs data file: %s", str(e)) return {} try: data = json.loads(raw_data) except (json.JSONDecodeError, ValueError): logger.error("Malformed file with whmcs data: %s", raw_data) return {} return data def save(self, data): """ Saves ALL data passed by WHMCS it should not have any validations deliberately to be as compatible as possible with current installed WHMCS plugin """ current_data = self.read() # no validation needed current_data.update(data) try: with open(self.path, "w") as file: json.dump(current_data, file, indent=4) except IOError as e: logger.error("Failed to write whmcs data to file: %s", str(e)) async def sync_billing_data(sink, data): my_imunify_updates = data.get(config.MY_IMUNIFY_KEY) return await mi_update(sink, my_imunify_updates) def convert_to_config_key_value(key, value): """ Convert several keys to config key, otherwise just return same key any key is acceptable """ if key == "status": return ( "enable", { "active": True, "inactive": False, }[value], ) elif key == "protection": return ( "protection", { "enabled": True, "disabled": False, }[value], ) elif key == "mu_plugin_installation": return "smart_advice_allowed", value return key, value def convert_from_config_key_value(key, value): """ Convert several keys from config format, otherwise just return same key any key is acceptable """ if key == "enable": return "status", ("active" if value else "inactive") elif key == "protection": return "protection", ("enabled" if value else "disabled") elif key == "smart_advice_allowed": return "mu_plugin_installation", value return key, value async def get_users(): return await hp.HostingPanel().get_users() async def mi_update(sink, requested_myimunify_data): """ Updates supported parameters if passed, otherwise does nothing updates 2 config parameters (if specified): status and purchase_page_url updates protection status for users (if specified) """ if not requested_myimunify_data: logger.info("Nothing to update for MyImunify") return whmcs_activation_status = requested_myimunify_data.get("status") if whmcs_activation_status: # no validation needed WhmcsConf().save({"status": requested_myimunify_data.get("status")}) await update_configs(sink, requested_myimunify_data) WordPressMuPlugin().prepare_for_mu_plugin_installation( whmcs_activation_status, requested_myimunify_data.get(MU_PLUGIN_INSTALLATION), ) if not requested_myimunify_data.get("protection"): return await get_current_whmcs_data([]) all_users = await get_users() target_users = requested_myimunify_data.get("users", []) or all_users filtered_passed_users = [ user for user in target_users if user in all_users ] if filtered_passed_users: logger.info( "Updating protection status for users=%s", str(filtered_passed_users), ) await update_users_protection( sink, filtered_passed_users, convert_to_config_key_value( "protection", requested_myimunify_data["protection"] )[1], ) else: logger.warning("No users to update protection for") return await get_current_whmcs_data(filtered_passed_users) async def update_configs(sink, requested_myimunify_data): # those params are stored in config mi_config_parameters = ( ["purchase_page_url"] if config.is_mi_freemium_license() else ["purchase_page_url", "status"] ) mi_config_data = dict( convert_to_config_key_value(param, value) for param, value in requested_myimunify_data.items() if param in mi_config_parameters ) mu_plugin_data = dict( convert_to_config_key_value(param, value) for param, value in requested_myimunify_data.items() if param in MU_PLUGIN_KEYS ) config_dict = {} if mi_config_data: config_dict[config.MY_IMUNIFY_KEY] = mi_config_data if mu_plugin_data: config_dict["CONTROL_PANEL"] = mu_plugin_data if config_dict: logger.info("Updating config with data: %s", str(config_dict)) # updates only 2 supported keys: purchase_page_url and status await update_config(sink, config_dict) async def get_users_info(users): """ Returns information from database based on passed users if no users passed - returns for all users """ result = ( MyImunify.select().where(MyImunify.user.in_(users)).dicts() if users else MyImunify.select().dicts() ) return [ { "user": item["user"], "protection": convert_from_config_key_value( "protection", item["protection"] )[1], } for item in result ] async def get_current_whmcs_data(users): """ Returns the current configuration and user protection status. {MY_IMUNIFY: {'status': 'active/inactive', 'purchase_page_url': 'SOMEURL', 'protection': []}} """ conf_data = config.ConfigFile().config_to_dict() current_config = dict( convert_from_config_key_value(param, value) for param, value in conf_data.get(config.MY_IMUNIFY_KEY, {}).items() ) cp_data = conf_data.get("CONTROL_PANEL") current_config[MU_PLUGIN_INSTALLATION] = convert_from_config_key_value( "smart_advice_allowed", cp_data.get("smart_advice_allowed") )[1] current_config[ADVICE_EMAIL_NOTIFICATION] = cp_data.get( ADVICE_EMAIL_NOTIFICATION ) current_config["protection"] = await get_users_info(users) return current_config def get_upgrade_url_link(username, domain): purchase_url_link = ( config.MyImunifyConfig.PURCHASE_PAGE_URL.rstrip("/") + "/?" + urllib.parse.urlencode( { "m": "cloudlinux_advantage", "action": "provisioning", "suite": "my_imunify_account_protection", "username": username, "domain": domain, "server_ip": hp.HostingPanel().get_server_ip(), } ) ) return purchase_url_link defence360agent/utils/wordpress_mu_plugin.py0000644000000000000000000000264000000000000016371 0ustar import os from logging import getLogger logger = getLogger(__name__) MU_PLUGIN_INSTALLATION = "mu_plugin_installation" ADVICE_EMAIL_NOTIFICATION = "advice_email_notification" MU_PLUGIN_KEYS = [MU_PLUGIN_INSTALLATION, ADVICE_EMAIL_NOTIFICATION] class WordPressMuPlugin: def prepare_for_mu_plugin_installation( self, activation_status, mu_plugin_status ): """ Must use plugin works only if cl-hosting-smart-advice is installed So it is a requirement to be sure it is installed It is expected to be installed by default with Imunify360 """ if not all([activation_status == "active", mu_plugin_status]): logger.warning( "Nothing to prepare for Must Use plugin as settings " "are not turned on, activation status=%s mu_plugin_status=%s", str(activation_status), str(mu_plugin_status), ) return if not mu_plugin_status: logger.warning( "Nothing to prepare for Must Use plugin " "as mu_plugin_status=%s", str(mu_plugin_status), ) return if not os.path.exists("/usr/sbin/cl-hosting-smart-advice"): raise ValueError( "cl-hosting-smart-advice rpm package is not installed " "in the system, please install it and try again" ) defence360agent/utils/zipsafe.py0000644000000000000000000000132000000000000013715 0ustar import zipfile from pathlib import Path def safe_extractall(zf: zipfile.ZipFile, dest: Path) -> None: dest_resolved = Path(dest).resolve() for member in zf.namelist(): if member.startswith(("/", "\\")): raise ValueError("Unsafe absolute zip member path: %r" % (member,)) parts = Path(member).parts if ".." in parts: raise ValueError( "Unsafe parent-traversal zip member path: %r" % (member,) ) target = (dest_resolved / member).resolve() if target != dest_resolved and dest_resolved not in target.parents: raise ValueError("Zip member escapes destination: %r" % (member,)) zf.extractall(dest_resolved) defence360agent/wordpress/0000755000000000000000000000000000000000000012576 5ustar defence360agent/wordpress/__init__.py0000644000000000000000000000235400000000000014713 0ustar """WordPress incident collection, sending logic, plugin management, and rules. Available for both AV and IM360 modes. """ from defence360agent.wordpress.changelog_processor import ( ChangelogProcessor, ) from defence360agent.wordpress.incident_collector import ( IncidentCollector, IncidentRateLimiter, ) from defence360agent.wordpress.incident_sender import IncidentSender from defence360agent.wordpress.incident_parser import IncidentFileParser from defence360agent.wordpress.wp_rules import ( WP_RULES_ZIP_FILENAME, WP_RULES_VERSION_FILENAME, find_file_in_index, extract_wp_rules_yaml, get_wp_rules_data, get_wp_ruleset_version, ) from defence360agent.wordpress.constants import ( PLUGIN_PATH, PLUGIN_SLUG, PLUGIN_VERSION_FILE, WP_CLI_WRAPPER_PATH, ) __all__ = [ "ChangelogProcessor", "IncidentCollector", "IncidentRateLimiter", "IncidentSender", "IncidentFileParser", # wp_rules exports "WP_RULES_ZIP_FILENAME", "WP_RULES_VERSION_FILENAME", "find_file_in_index", "extract_wp_rules_yaml", "get_wp_rules_data", "get_wp_ruleset_version", # constants exports "PLUGIN_PATH", "PLUGIN_SLUG", "PLUGIN_VERSION_FILE", "WP_CLI_WRAPPER_PATH", ] defence360agent/wordpress/__pycache__/0000755000000000000000000000000000000000000015006 5ustar defence360agent/wordpress/__pycache__/__init__.cpython-311.opt-1.pyc0000644000000000000000000000264000000000000022210 0ustar r_j~dZddlmZddlmZmZddlmZddlm Z ddl m Z m Z m Z mZmZmZddlmZmZmZmZgdZd S) ztWordPress incident collection, sending logic, plugin management, and rules. Available for both AV and IM360 modes. )ChangelogProcessor)IncidentCollectorIncidentRateLimiter)IncidentSender)IncidentFileParser)WP_RULES_ZIP_FILENAMEWP_RULES_VERSION_FILENAMEfind_file_in_indexextract_wp_rules_yamlget_wp_rules_dataget_wp_ruleset_version) PLUGIN_PATH PLUGIN_SLUGPLUGIN_VERSION_FILEWP_CLI_WRAPPER_PATH)rrrrrrr r r r r rrrrN)__doc__-defence360agent.wordpress.changelog_processorr,defence360agent.wordpress.incident_collectorrr)defence360agent.wordpress.incident_senderr)defence360agent.wordpress.incident_parserr"defence360agent.wordpress.wp_rulesrr r r r r #defence360agent.wordpress.constantsrrrr__all__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/__init__.pyrs EDDDDDHHHHHH   rdefence360agent/wordpress/__pycache__/__init__.cpython-311.pyc0000644000000000000000000000264000000000000021251 0ustar r_j~dZddlmZddlmZmZddlmZddlm Z ddl m Z m Z m Z mZmZmZddlmZmZmZmZgdZd S) ztWordPress incident collection, sending logic, plugin management, and rules. Available for both AV and IM360 modes. )ChangelogProcessor)IncidentCollectorIncidentRateLimiter)IncidentSender)IncidentFileParser)WP_RULES_ZIP_FILENAMEWP_RULES_VERSION_FILENAMEfind_file_in_indexextract_wp_rules_yamlget_wp_rules_dataget_wp_ruleset_version) PLUGIN_PATH PLUGIN_SLUGPLUGIN_VERSION_FILEWP_CLI_WRAPPER_PATH)rrrrrrr r r r r rrrrN)__doc__-defence360agent.wordpress.changelog_processorr,defence360agent.wordpress.incident_collectorrr)defence360agent.wordpress.incident_senderr)defence360agent.wordpress.incident_parserr"defence360agent.wordpress.wp_rulesrr r r r r #defence360agent.wordpress.constantsrrrr__all__W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/__init__.pyrs EDDDDDHHHHHH   rdefence360agent/wordpress/__pycache__/bot_protection.cpython-311.opt-1.pyc0000644000000000000000000001374400000000000023512 0ustar r_j ^dZddlZddlZddlZddlZddlmZejeZ dZ dZ dZ ej dejZej dejZej d ejZej d ejZd ed efd Zd ed efdZd ed efdZdeded ededef dZdS)asResolve the AI bot protection state actually applied on a WP site. Mirrors the precedence the imunify-security plugin applies at runtime (inc/App/Plugin.php::isBotProtectionActive, inc/App/Bot/Preset.php::resolve): a site-owner wp-config.php constant or bot-settings.php override wins over the hoster-written plugin_config.php default. Files are parsed, never executed. N)Path)balancedstrictmonitorrizDdefine\(\s*['"]IMUNIFY_AI_BOT_PROTECTION['"]\s*,\s*(true|false)\s*\)zPdefine\(\s*['\"]IMUNIFY_AI_BOT_PROTECTION_PRESET['\"]\s*,\s*['\"](\w+)['\"]\s*\)z#['"]enabled['"]\s*=>\s*(true|false)z#['"]preset['"]\s*=>\s*['"](\w+)['"]pathuidc< tj|tjtjztjz}n#t $rYdSwxYw tj|}tj|j r |j |kr tj |dStj |tddtj |S#t $rYtj |dSwxYw#tj |wxYw)zRead a small site-owner config file as root, defensively. Returns None (so the caller falls back to the hoster default) on a symlink, FIFO/device, a file not owned by the site user, or any I/O error. At most _MAX_BYTES are read. Nzutf-8replace)errors)osopenO_RDONLY O_NOFOLLOW O_NONBLOCKOSErrorfstatstatS_ISREGst_modest_uidcloseread _MAX_BYTESdecode)rrfdinfos ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/bot_protection.py _safe_readr.s WT2;6F G G ttx|||DL)) T[C-?-?  wr:&&--gi-HH       s99< A  A 8C .C D*DDDDct||}|dSd}t|}|r+|ddk}d}t |}|rU|dt vr'|d}||fS)zoReturn (enabled, preset) from wp-config.php constants; each is None when the constant is absent or invalid.N)NNtrue)r_CONST_ENABLEDsearchgrouplower _CONST_PRESET VALID_PRESETS)rrtextenabledmatchpresets r_parse_wp_configr,Ds dC D |zG  ! !$ ' 'E 3++a..&&((F2 F   & &E (Q%%''=88Q%%'' F?ct||}|dSt|}|r+|ddknd}d}t |}|rU|dt vr'|d}||fS)zReturn (enabled, preset) from the site-owner bot-settings.php. A missing/unreadable file means enabled with no explicit preset, matching the plugin's OptOutFlag default.N)TNr r!T)r _KV_ENABLEDr#r$r% _KV_PRESETr')rrr(r*r)r+s r_parse_bot_settingsr1Us dC D |z   t $ $E27Aekk!nn""$$..TG F   d # #E (Q%%''=88Q%%'' F?r-docrootdata_dirhoster_enabled hoster_presetctt|dz |\}}tt|dz |\}}t|o|} |durd} |||fD]} | tvr| | fcS| t fS)aResolve the effective (enabled, preset) for a site. enabled: the wp-config constant (if set to false) force-disables; otherwise it is the AND of the hoster default and the site-owner flag. preset: first match of wp-config constant, bot-settings.php, hoster. z wp-config.phpzbot-settings.phpF)r,rr1boolr'DEFAULT_PRESET) r2r3rr4r5 const_enabled const_preset bot_enabled bot_presetr) candidates rresolve_ai_bot_protectionr>es#3 W '##M<2 X++SK>""2{G"J >&&  % %I% % % % & N ""r-)__doc__loggingr rerpathlibr getLogger__name__loggerr'r8rcompile IGNORECASEr"r&r/r0intrr,r1strr7r>r-rrKs   8 $ $1   OM M bj.  RZ.  T,4c" d      # ## # #  ######r-defence360agent/wordpress/__pycache__/bot_protection.cpython-311.pyc0000644000000000000000000001374400000000000022553 0ustar r_j ^dZddlZddlZddlZddlZddlmZejeZ dZ dZ dZ ej dejZej dejZej d ejZej d ejZd ed efd Zd ed efdZd ed efdZdeded ededef dZdS)asResolve the AI bot protection state actually applied on a WP site. Mirrors the precedence the imunify-security plugin applies at runtime (inc/App/Plugin.php::isBotProtectionActive, inc/App/Bot/Preset.php::resolve): a site-owner wp-config.php constant or bot-settings.php override wins over the hoster-written plugin_config.php default. Files are parsed, never executed. N)Path)balancedstrictmonitorrizDdefine\(\s*['"]IMUNIFY_AI_BOT_PROTECTION['"]\s*,\s*(true|false)\s*\)zPdefine\(\s*['\"]IMUNIFY_AI_BOT_PROTECTION_PRESET['\"]\s*,\s*['\"](\w+)['\"]\s*\)z#['"]enabled['"]\s*=>\s*(true|false)z#['"]preset['"]\s*=>\s*['"](\w+)['"]pathuidc< tj|tjtjztjz}n#t $rYdSwxYw tj|}tj|j r |j |kr tj |dStj |tddtj |S#t $rYtj |dSwxYw#tj |wxYw)zRead a small site-owner config file as root, defensively. Returns None (so the caller falls back to the hoster default) on a symlink, FIFO/device, a file not owned by the site user, or any I/O error. At most _MAX_BYTES are read. Nzutf-8replace)errors)osopenO_RDONLY O_NOFOLLOW O_NONBLOCKOSErrorfstatstatS_ISREGst_modest_uidcloseread _MAX_BYTESdecode)rrfdinfos ]/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/bot_protection.py _safe_readr.s WT2;6F G G ttx|||DL)) T[C-?-?  wr:&&--gi-HH       s99< A  A 8C .C D*DDDDct||}|dSd}t|}|r+|ddk}d}t |}|rU|dt vr'|d}||fS)zoReturn (enabled, preset) from wp-config.php constants; each is None when the constant is absent or invalid.N)NNtrue)r_CONST_ENABLEDsearchgrouplower _CONST_PRESET VALID_PRESETS)rrtextenabledmatchpresets r_parse_wp_configr,Ds dC D |zG  ! !$ ' 'E 3++a..&&((F2 F   & &E (Q%%''=88Q%%'' F?ct||}|dSt|}|r+|ddknd}d}t |}|rU|dt vr'|d}||fS)zReturn (enabled, preset) from the site-owner bot-settings.php. A missing/unreadable file means enabled with no explicit preset, matching the plugin's OptOutFlag default.N)TNr r!T)r _KV_ENABLEDr#r$r% _KV_PRESETr')rrr(r*r)r+s r_parse_bot_settingsr1Us dC D |z   t $ $E27Aekk!nn""$$..TG F   d # #E (Q%%''=88Q%%'' F?r-docrootdata_dirhoster_enabled hoster_presetctt|dz |\}}tt|dz |\}}t|o|} |durd} |||fD]} | tvr| | fcS| t fS)aResolve the effective (enabled, preset) for a site. enabled: the wp-config constant (if set to false) force-disables; otherwise it is the AND of the hoster default and the site-owner flag. preset: first match of wp-config constant, bot-settings.php, hoster. z wp-config.phpzbot-settings.phpF)r,rr1boolr'DEFAULT_PRESET) r2r3rr4r5 const_enabled const_preset bot_enabled bot_presetr) candidates rresolve_ai_bot_protectionr>es#3 W '##M<2 X++SK>""2{G"J >&&  % %I% % % % & N ""r-)__doc__loggingr rerpathlibr getLogger__name__loggerr'r8rcompile IGNORECASEr"r&r/r0intrr,r1strr7r>r-rrKs   8 $ $1   OM M bj.  RZ.  T,4c" d      # ## # #  ######r-defence360agent/wordpress/__pycache__/changelog_processor.cpython-311.opt-1.pyc0000644000000000000000000004147300000000000024506 0ustar r_j2dZddlZddlZddlZddlmZddlmZddlm Z m Z ddl m Z ddl mZmZddlmZdd lmZdd lmZdd lmZdd lmZejeZd ZdZdZdZ GddZ!dS)aProcessor for WordPress rule disable/enable changelog files. The PHP WordPress plugin writes rule change actions to changelog.php when a user disables or enables protection rules from the WordPress admin panel. This module reads, parses, and applies those actions to the agent database. The changelog.php file uses the same format as incident files: ||kr8t d | dd|j||| |||rd }|||||d{V#t$r%} t d | Yd} ~ d} ~ wt $r-} t d ||j| Yd} ~ 2d} ~ wwxYwt d|jt|||S)amParse and apply actions from a changelog file. The file is always deleted after reading, even on parse errors. Actions older than the last sync timestamp are skipped to prevent stale changelog files (e.g. from backup restores) from undoing more recent changes. Returns: True if any DB changes occurred. FNzSWP WAF rule editing disabled by policy; dropping %d changelog action(s) for site %stsrz:Missing or invalid timestamp in changelog action for rule rule_id? on site zPSkipping stale changelog action for rule %s on site %s (ts=%.0f <= sync_ts=%.0f)Tz$Skipping invalid changelog entry: %sz5Failed to process changelog action %s for site %s: %sz9Processed changelog for site %s: %d action(s), changed=%s)r6rrstruidrr r!r,_get_last_sync_tsfloatgetr5_process_action_report_actionwarningr*r+) rr.r#ractions last_sync_tschangedaction timestampr/s rr(z*ChangelogProcessor._process_changelog_files )).$?? 5$$5s48}}EEEEEEEE  KK3G      5--d33   F !&**T1"5"566 >>$3%+ZZ 3%?%?33$(L33  + \0I0IKK@ 9c22 !$ ''i@@#"G))&$iHHHHHHHHHH J J JEqIIIIIIII    KL    G L LL     s+BE&.7E&& G 0F G "GG rGrHcB|d}|d}|r|std||tkr||||S|tkr|||Std|d|d|j)aApply a single changelog action to the database. Args: action: Parsed action dict with keys: action, rule_id, ts. site: The WordPress site the action belongs to. timestamp: Pre-resolved Unix timestamp for this action. Returns: True if the database state was modified. Raises: ValueError: If the action is missing required fields or has an unknown action type. rGr9z.Missing action or rule_id in changelog entry: zUnknown changelog action 'z ' for rule r;)r@r5ACTION_DISABLE_apply_disable ACTION_ENABLE _apply_enabler,)rrGr#rH action_typer9s rrAz"ChangelogProcessor._process_actions"jj** **Y'' ' III  . ( (&&wi@@ @ M ) )%%gt44 4>[>>$>>/3|>> rcp tj|j}|jS#tj$rYdSwxYw)zGet the last disabled-rules sync timestamp for a site. Returns None if the site has no DB record or no sync timestamp, meaning all actions should be processed. N)r get_by_idr,disabled_rules_sync_ts DoesNotExist)r#db_sites rr>z$ChangelogProcessor._get_last_sync_tssG #-dl;;G1 1)   44 s "55r9cjtj||jgtj|j|}|dkS)zApply a disable action from the changelog. Returns: True if a new disable entry was created (not a no-op). )r9domainssourceuser_idrHr)r storedomainSOURCE_WORDPRESSr=)r9r#rHcounts rrKz!ChangelogProcessor._apply_disables?$[M!2H    qyrcFtj||jg}|dkS)zvApply an enable action from the changelog. Returns: True if a disable entry was removed. )r9rUr)r removerY)rr9r#r[s rrMz ChangelogProcessor._apply_enable$s1 %[M   qyrc K|dS|d}|d}|tkr tj}n|tkr tj}ndS ||d||jg||jtj d{VdS#t$r-}t d||j |Yd}~dSd}~wwxYw)zSend a rule change event to the correlation server. Must only be called for valid actions (after _process_action succeeds). NrGr9 wordpress) plugin_idrulerUrHrWrVz B BBBB C(# C(,1C##C(,2DE0!EE E::F  F )rN)__name__ __module__ __qualname____doc__rlistrrr$boolrrdictr6r(r?rA staticmethodr>r<rKrMrBr)rrrr.sw  ++++ F|D  f :$$D $  $$$$L"*0 d4GGGD G  GGGGR  "( 5:      D  54<   \   6 e    \  S  4    (((D ( (  (((\(T););); );););\);););rr)"r~rologgingrjpathlibr"defence360agent.contracts.messagesr%defence360agent.contracts.permissionsrr!defence360agent.contracts.pluginsrdefence360agent.model.wordpressrr &defence360agent.model.wp_disabled_ruler defence360agent.utils.fd_opsr defence360agent.wordpress.clir )defence360agent.wordpress.incident_parserr defence360agent.wordpress.utilsr getLoggerr{rr'rirJrLrrrrrsb$  :::::::99999AAAAAAAAAAAAAA666666666666HHHHHHHHHHHH  8 $ $$. W;W;W;W;W;W;W;W;W;W;rdefence360agent/wordpress/__pycache__/changelog_processor.cpython-311.pyc0000644000000000000000000004147300000000000023547 0ustar r_j2dZddlZddlZddlZddlmZddlmZddlm Z m Z ddl m Z ddl mZmZddlmZdd lmZdd lmZdd lmZdd lmZejeZd ZdZdZdZ GddZ!dS)aProcessor for WordPress rule disable/enable changelog files. The PHP WordPress plugin writes rule change actions to changelog.php when a user disables or enables protection rules from the WordPress admin panel. This module reads, parses, and applies those actions to the agent database. The changelog.php file uses the same format as incident files: ||kr8t d | dd|j||| |||rd }|||||d{V#t$r%} t d | Yd} ~ d} ~ wt $r-} t d ||j| Yd} ~ 2d} ~ wwxYwt d|jt|||S)amParse and apply actions from a changelog file. The file is always deleted after reading, even on parse errors. Actions older than the last sync timestamp are skipped to prevent stale changelog files (e.g. from backup restores) from undoing more recent changes. Returns: True if any DB changes occurred. FNzSWP WAF rule editing disabled by policy; dropping %d changelog action(s) for site %stsrz:Missing or invalid timestamp in changelog action for rule rule_id? on site zPSkipping stale changelog action for rule %s on site %s (ts=%.0f <= sync_ts=%.0f)Tz$Skipping invalid changelog entry: %sz5Failed to process changelog action %s for site %s: %sz9Processed changelog for site %s: %d action(s), changed=%s)r6rrstruidrr r!r,_get_last_sync_tsfloatgetr5_process_action_report_actionwarningr*r+) rr.r#ractions last_sync_tschangedaction timestampr/s rr(z*ChangelogProcessor._process_changelog_files )).$?? 5$$5s48}}EEEEEEEE  KK3G      5--d33   F !&**T1"5"566 >>$3%+ZZ 3%?%?33$(L33  + \0I0IKK@ 9c22 !$ ''i@@#"G))&$iHHHHHHHHHH J J JEqIIIIIIII    KL    G L LL     s+BE&.7E&& G 0F G "GG rGrHcB|d}|d}|r|std||tkr||||S|tkr|||Std|d|d|j)aApply a single changelog action to the database. Args: action: Parsed action dict with keys: action, rule_id, ts. site: The WordPress site the action belongs to. timestamp: Pre-resolved Unix timestamp for this action. Returns: True if the database state was modified. Raises: ValueError: If the action is missing required fields or has an unknown action type. rGr9z.Missing action or rule_id in changelog entry: zUnknown changelog action 'z ' for rule r;)r@r5ACTION_DISABLE_apply_disable ACTION_ENABLE _apply_enabler,)rrGr#rH action_typer9s rrAz"ChangelogProcessor._process_actions"jj** **Y'' ' III  . ( (&&wi@@ @ M ) )%%gt44 4>[>>$>>/3|>> rcp tj|j}|jS#tj$rYdSwxYw)zGet the last disabled-rules sync timestamp for a site. Returns None if the site has no DB record or no sync timestamp, meaning all actions should be processed. N)r get_by_idr,disabled_rules_sync_ts DoesNotExist)r#db_sites rr>z$ChangelogProcessor._get_last_sync_tssG #-dl;;G1 1)   44 s "55r9cjtj||jgtj|j|}|dkS)zApply a disable action from the changelog. Returns: True if a new disable entry was created (not a no-op). )r9domainssourceuser_idrHr)r storedomainSOURCE_WORDPRESSr=)r9r#rHcounts rrKz!ChangelogProcessor._apply_disables?$[M!2H    qyrcFtj||jg}|dkS)zvApply an enable action from the changelog. Returns: True if a disable entry was removed. )r9rUr)r removerY)rr9r#r[s rrMz ChangelogProcessor._apply_enable$s1 %[M   qyrc K|dS|d}|d}|tkr tj}n|tkr tj}ndS ||d||jg||jtj d{VdS#t$r-}t d||j |Yd}~dSd}~wwxYw)zSend a rule change event to the correlation server. Must only be called for valid actions (after _process_action succeeds). NrGr9 wordpress) plugin_idrulerUrHrWrVz B BBBB C(# C(,1C##C(,2DE0!EE E::F  F )rN)__name__ __module__ __qualname____doc__rlistrrr$boolrrdictr6r(r?rA staticmethodr>r<rKrMrBr)rrrr.sw  ++++ F|D  f :$$D $  $$$$L"*0 d4GGGD G  GGGGR  "( 5:      D  54<   \   6 e    \  S  4    (((D ( (  (((\(T););); );););\);););rr)"r~rologgingrjpathlibr"defence360agent.contracts.messagesr%defence360agent.contracts.permissionsrr!defence360agent.contracts.pluginsrdefence360agent.model.wordpressrr &defence360agent.model.wp_disabled_ruler defence360agent.utils.fd_opsr defence360agent.wordpress.clir )defence360agent.wordpress.incident_parserr defence360agent.wordpress.utilsr getLoggerr{rr'rirJrLrrrrrsb$  :::::::99999AAAAAAAAAAAAAA666666666666HHHHHHHHHHHH  8 $ $$. W;W;W;W;W;W;W;W;W;W;rdefence360agent/wordpress/__pycache__/cli.cpython-311.opt-1.pyc0000644000000000000000000003730700000000000021230 0ustar r_j*ddlZddlZddlZddlZddlmZddlmZddlm Z m Z m Z ddl m Z mZddlmZmZmZddlmZddlmZejeZd ed efd Zd ed efd Zded efdZdefdZdefdZ defdZ!ded efdZ"defdZ#defdZ$defdZ%defdZ&defdZ'e ddefdZ(dZ)defdZ*dS)N)Path) StrictVersion) check_run CheckRunErrorasync_lru_cache) PLUGIN_PATH PLUGIN_SLUG)build_command_for_userget_php_binary_path wp_wrapper) log_message)WPSite version_strreturnct|tsdS|}|sdS t|dS#t$rYdSwxYw)z1Validate if a string is a valid semantic version.FT) isinstancestrstripr ValueError)r trimmed_strs R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/cli.py_validate_semverrsu k3 ' 'u##%%K uk"""t uusA AAoutputcb|sdS|}|sdSt|dkr+|d}t|r|St|dkr+|d}t|r|St dd|iddd dS) z Extract version from WP CLI output, trying both first and last parts. Args: output: The raw output from WP CLI Returns: The extracted version string or None if no valid version found NrzMFailed to extract valid semver version from WP CLI output. Output: '{output}'rwarning wordpressz#wp-plugin-version-extraction-failed) format_argslevel component fingerprint)splitlenrrr )rparts first_part last_parts r_extract_version_from_outputr(+s t LLNNE t 5zzA~~1X^^%% J ' '   5zzA~~"IOO%% I & &   v&9  4sitecKt|d{V}|dz dz dz }|sdStjd} t |5}|D][}||}|rB|d}t|r|ccdddSddddS\ dddn #1swxYwYn4#t$r'}t d||Yd}~dSd}~wwxYwdS)z Parse the version of imunify-security plugin by reading the main plugin file. Args: site: WordPress site object containing docroot path Returns: str: Plugin version or None if not found or invalid Npluginsimunify-securityzimunify-security.phpz\* Version:\s*([0-9.]+)rz=Failed to read plugin file to determine version number %s: %s) get_content_direxistsrecompileopensearchgroupr Exceptionloggererror) r* content_dir plugin_fileversion_patternflinematchversiones r_parse_version_from_plugin_filer@Ws(--------Ki"447MM     tj!;<Ktj|jj}t ||d{V}gt ||jddtd}t||}t d|t|d{VdS)z L   ttttt  DaHHHttttts% A C D7 D D7D22D7cK t|d{V}|r|Sn2#t$r%}td|Yd}~nd}~wwxYwtdt |d{VS)z Get the version of the imunify-security wp plugin installed on given WordPress site. First tries to parse the version from the plugin file, then falls back to WP CLI. Nz,Failed to parse version from plugin file: %sz/Plugin version not found in file, trying WP CLI)r@r5r6rrLra)r*r>r?s rget_plugin_versionrcsJ7========  N  JJJEqIIIIIIIIJ KKABBB$T** * * * * * **s A AA c`Ktj|jj}t ||d{V}gt ||jddt}t||}t d| t|d{Vn#t$rYdSwxYwdS)zMCheck if the imunify-security wp plugin is installed on given WordPress site.NrB is-installedz#Checking if wp plugin is installed FT) rGrHrIrJr r rKr r r6rLrrrMs ris_plugin_installedrf s|DH%%-H(x88888888H  Hdl + +       D %Xt44G KK?g??@@@          uu 4sB B+*B+cRKtj|jj}t ||d{V}gt ||jdd}t||}t d| t|d{Vn#t$rYdSwxYwdS)zJCheck if WordPress is installed and given site is accessible using WP CLI.Ncorerez#Checking if WordPress is installed FT) rGrHrIrJr r rKr r6rLrrrMs ris_wordpress_installedri!s|DH%%-H(x88888888H  Hdl + +    D %Xt44G KK?g??@@@          uu 4sB B$#B$cKtj|jj}t ||d{V}gt ||jdd}t||}t d| tj t|dd{V}| dS#tj$r$td|jYdSt"$r+}td |jYd}~dSd}~wt($r&}td |Yd}~dSd}~wwxYw) z Get the content directory of the WordPress site using WP CLI. This should only be used if the default wp-content directory does not exist. Nevalzecho WP_CONTENT_DIR;zGetting content directory )timeoutr\z1WP-CLI timed out getting content directory for %sz0Failed to get content directory. Return code: %sz-Failed to decode content directory output: %s)rGrHrIrJr r rKr r6rLasynciowait_forrr]r TimeoutErrorrrr7r^r_)r*rNrOrPrQr`r?s r_get_content_directoryrq8s |DH%%-H(x88888888H  Hdl + +    D %Xt44G KK6W66777' '(:(:BGGGGGGGGG}}W%%++---   ?   tt  > L   ttttt  DaHHHttttts+AC/E$ E$ D11 E$>EE$d)maxsizecKt|jdz }|r|s&t |d{V}|rt|}|S)a Get the WordPress content directory for the given WordPress site. This function first checks if the default wp-content directory exists at the site's docroot. If the default path doesn't exist or isn't a directory, it attempts to get the actual content directory using WordPress CLI's WP_CONTENT_DIR constant. Returns: Path: The WordPress content directory path wp-contentN)rrKr/is_dirrq)r*r8wp_content_dirs rr.r.\st|$$|3K     /{'9'9';';/5d;;;;;;;;  /~..K r)c8tdS)z.Clear the async LRU cache for get_content_dir.N)r. cache_clearr)rclear_get_content_dir_cacher{ts!!!!!r)cKt|d{V}|st|jdz }t|dz S)zO Get the Imunify Security data directory for the given WordPress site. Nrur-)r.rrK)r*r8s r get_data_dirr}ysW(--------K 84<((<7   1 11r))+rnloggingrGr0pathlibrdistutils.versionrdefence360agent.utilsrrr#defence360agent.wordpress.constantsrr defence360agent.wordpress.utilsr r r defence360agent.sentryr defence360agent.model.wordpressr getLogger__name__r6rboolrr(r@rRrTrWrYrarcrfrirqr.r{r}rzr)rrs ++++++ IHHHHHHH /.....222222  8 $ $#$$))))))X&&3&&&&Rv*f2(V$FD+6++++&F0v.!v!!!!H.""" 2V222222r)defence360agent/wordpress/__pycache__/cli.cpython-311.pyc0000644000000000000000000003730700000000000020271 0ustar r_j*ddlZddlZddlZddlZddlmZddlmZddlm Z m Z m Z ddl m Z mZddlmZmZmZddlmZddlmZejeZd ed efd Zd ed efd Zded efdZdefdZdefdZ defdZ!ded efdZ"defdZ#defdZ$defdZ%defdZ&defdZ'e ddefdZ(dZ)defdZ*dS)N)Path) StrictVersion) check_run CheckRunErrorasync_lru_cache) PLUGIN_PATH PLUGIN_SLUG)build_command_for_userget_php_binary_path wp_wrapper) log_message)WPSite version_strreturnct|tsdS|}|sdS t|dS#t$rYdSwxYw)z1Validate if a string is a valid semantic version.FT) isinstancestrstripr ValueError)r trimmed_strs R/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/cli.py_validate_semverrsu k3 ' 'u##%%K uk"""t uusA AAoutputcb|sdS|}|sdSt|dkr+|d}t|r|St|dkr+|d}t|r|St dd|iddd dS) z Extract version from WP CLI output, trying both first and last parts. Args: output: The raw output from WP CLI Returns: The extracted version string or None if no valid version found NrzMFailed to extract valid semver version from WP CLI output. Output: '{output}'rwarning wordpressz#wp-plugin-version-extraction-failed) format_argslevel component fingerprint)splitlenrrr )rparts first_part last_parts r_extract_version_from_outputr(+s t LLNNE t 5zzA~~1X^^%% J ' '   5zzA~~"IOO%% I & &   v&9  4sitecKt|d{V}|dz dz dz }|sdStjd} t |5}|D][}||}|rB|d}t|r|ccdddSddddS\ dddn #1swxYwYn4#t$r'}t d||Yd}~dSd}~wwxYwdS)z Parse the version of imunify-security plugin by reading the main plugin file. Args: site: WordPress site object containing docroot path Returns: str: Plugin version or None if not found or invalid Npluginsimunify-securityzimunify-security.phpz\* Version:\s*([0-9.]+)rz=Failed to read plugin file to determine version number %s: %s) get_content_direxistsrecompileopensearchgroupr Exceptionloggererror) r* content_dir plugin_fileversion_patternflinematchversiones r_parse_version_from_plugin_filer@Ws(--------Ki"447MM     tj!;<Ktj|jj}t ||d{V}gt ||jddtd}t||}t d|t|d{VdS)z L   ttttt  DaHHHttttts% A C D7 D D7D22D7cK t|d{V}|r|Sn2#t$r%}td|Yd}~nd}~wwxYwtdt |d{VS)z Get the version of the imunify-security wp plugin installed on given WordPress site. First tries to parse the version from the plugin file, then falls back to WP CLI. Nz,Failed to parse version from plugin file: %sz/Plugin version not found in file, trying WP CLI)r@r5r6rrLra)r*r>r?s rget_plugin_versionrcsJ7========  N  JJJEqIIIIIIIIJ KKABBB$T** * * * * * **s A AA c`Ktj|jj}t ||d{V}gt ||jddt}t||}t d| t|d{Vn#t$rYdSwxYwdS)zMCheck if the imunify-security wp plugin is installed on given WordPress site.NrB is-installedz#Checking if wp plugin is installed FT) rGrHrIrJr r rKr r r6rLrrrMs ris_plugin_installedrf s|DH%%-H(x88888888H  Hdl + +       D %Xt44G KK?g??@@@          uu 4sB B+*B+cRKtj|jj}t ||d{V}gt ||jdd}t||}t d| t|d{Vn#t$rYdSwxYwdS)zJCheck if WordPress is installed and given site is accessible using WP CLI.Ncorerez#Checking if WordPress is installed FT) rGrHrIrJr r rKr r6rLrrrMs ris_wordpress_installedri!s|DH%%-H(x88888888H  Hdl + +    D %Xt44G KK?g??@@@          uu 4sB B$#B$cKtj|jj}t ||d{V}gt ||jdd}t||}t d| tj t|dd{V}| dS#tj$r$td|jYdSt"$r+}td |jYd}~dSd}~wt($r&}td |Yd}~dSd}~wwxYw) z Get the content directory of the WordPress site using WP CLI. This should only be used if the default wp-content directory does not exist. Nevalzecho WP_CONTENT_DIR;zGetting content directory )timeoutr\z1WP-CLI timed out getting content directory for %sz0Failed to get content directory. Return code: %sz-Failed to decode content directory output: %s)rGrHrIrJr r rKr r6rLasynciowait_forrr]r TimeoutErrorrrr7r^r_)r*rNrOrPrQr`r?s r_get_content_directoryrq8s |DH%%-H(x88888888H  Hdl + +    D %Xt44G KK6W66777' '(:(:BGGGGGGGGG}}W%%++---   ?   tt  > L   ttttt  DaHHHttttts+AC/E$ E$ D11 E$>EE$d)maxsizecKt|jdz }|r|s&t |d{V}|rt|}|S)a Get the WordPress content directory for the given WordPress site. This function first checks if the default wp-content directory exists at the site's docroot. If the default path doesn't exist or isn't a directory, it attempts to get the actual content directory using WordPress CLI's WP_CONTENT_DIR constant. Returns: Path: The WordPress content directory path wp-contentN)rrKr/is_dirrq)r*r8wp_content_dirs rr.r.\st|$$|3K     /{'9'9';';/5d;;;;;;;;  /~..K r)c8tdS)z.Clear the async LRU cache for get_content_dir.N)r. cache_clearr)rclear_get_content_dir_cacher{ts!!!!!r)cKt|d{V}|st|jdz }t|dz S)zO Get the Imunify Security data directory for the given WordPress site. Nrur-)r.rrK)r*r8s r get_data_dirr}ysW(--------K 84<((<7   1 11r))+rnloggingrGr0pathlibrdistutils.versionrdefence360agent.utilsrrr#defence360agent.wordpress.constantsrr defence360agent.wordpress.utilsr r r defence360agent.sentryr defence360agent.model.wordpressr getLogger__name__r6rboolrr(r@rRrTrWrYrarcrfrirqr.r{r}rzr)rrs ++++++ IHHHHHHH /.....222222  8 $ $#$$))))))X&&3&&&&Rv*f2(V$FD+6++++&F0v.!v!!!!H.""" 2V222222r)defence360agent/wordpress/__pycache__/constants.cpython-311.opt-1.pyc0000644000000000000000000000124700000000000022467 0ustar r_jZ\dZddlmZedZdZedZedZdS)zConstants for WordPress module.)Pathz5/usr/share/imunify360/wp-plugins/imunify-security.zipzimunify-securityz9/usr/share/imunify360/wp-plugins/imunify-security.versionz//usr/share/imunify360/wp-plugins/wp-cli-wrapperN)__doc__pathlibr PLUGIN_PATH PLUGIN_SLUGPLUGIN_VERSION_FILEWP_CLI_WRAPPER_PATHX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/constants.pyr s`%%dJKK  d?dLMMr defence360agent/wordpress/__pycache__/constants.cpython-311.pyc0000644000000000000000000000124700000000000021530 0ustar r_jZ\dZddlmZedZdZedZedZdS)zConstants for WordPress module.)Pathz5/usr/share/imunify360/wp-plugins/imunify-security.zipzimunify-securityz9/usr/share/imunify360/wp-plugins/imunify-security.versionz//usr/share/imunify360/wp-plugins/wp-cli-wrapperN)__doc__pathlibr PLUGIN_PATH PLUGIN_SLUGPLUGIN_VERSION_FILEWP_CLI_WRAPPER_PATHX/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/constants.pyr s`%%dJKK  d?dLMMr defence360agent/wordpress/__pycache__/exception.cpython-311.opt-1.pyc0000644000000000000000000000134700000000000022452 0ustar r_j^"GddeZdS)ceZdZfdZxZS)PHPErrorcJt|dS)N)super__init__)selfmessage __class__s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/exception.pyrzPHPError.__init__s! !!!!!)__name__ __module__ __qualname__r __classcell__)r s@r rrs8"""""""""r rN) Exceptionrr r rs9"""""y"""""r defence360agent/wordpress/__pycache__/exception.cpython-311.pyc0000644000000000000000000000134700000000000021513 0ustar r_j^"GddeZdS)ceZdZfdZxZS)PHPErrorcJt|dS)N)super__init__)selfmessage __class__s X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/exception.pyrzPHPError.__init__s! !!!!!)__name__ __module__ __qualname__r __classcell__)r s@r rrs8"""""""""r rN) Exceptionrr r rs9"""""y"""""r defence360agent/wordpress/__pycache__/incident_collector.cpython-311.opt-1.pyc0000644000000000000000000006003600000000000024317 0ustar r_j,SdZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZmZmZejeZd Zd Zd Zd ZGd dZGddZdS)z1Collector for WordPress CVE protection incidents.N)Path) defaultdict)WPSite) get_data_dir)IncidentFileParser)aggregate_incident_dictsbulk_create_wordpress_incidentsbuild_incident_dictcountry_readerz.processing.phpz .failed.phpz .stored.phpc peZdZdZ ddededefdZd Z dd ed ed edee effdZ d ed efdZ dS)IncidentRateLimitera Rate limiter to prevent DoS attacks via incident flooding. Implements per-rule-per-IP rate limiting as per spec: - Maximum 100 incidents for each rule from the same IP within 15 minutes Memory-optimized implementation with bounded entry count using LRU eviction. dr 'max_incidents_per_rule_per_iptime_window_secondsmax_unique_entriesc||_||_||_tt|_d|_tj|_dS)aI Initialize the rate limiter. Args: max_incidents_per_rule_per_ip: Max incidents per rule per IP (default: 100) time_window_seconds: Time window in seconds (default: 900 = 15 minutes) max_unique_entries: Max unique (rule_id, IP) combinations to track (default: 10000) <N) max_per_rule_per_ip time_windowrrlistincident_timescleanup_intervaltime last_cleanup)selfrrrs a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_collector.py__init__zIncidentRateLimiter.__init__5sK$A ."4*$// " IKKc~ tj}||jz g}|jD]5\}} fd|D}|r ||j|< ||6|D] }|j|= t |j|jkrt|jd}tdt |jt|jdzz }|d|D] \}}|j|=t d|j|||_ dS)zHRemove records older than the time window and enforce max entries limit.c g|] }|k| Sr#.0tscutoffs r z.V===Rfbr c2|dr|ddndS)Nrr#)xs rz:IncidentRateLimiter._cleanup_old_records..es14ad1gg1r )keyr+g?NzARate limiter exceeded max entries (%d), removed %d oldest entries) rrritemsappendlenrsortedmaxintloggerwarningr) rnowkeys_to_deleter. timestampsrecententries_by_age num_to_remove_r's @r_cleanup_old_recordsz(IncidentRateLimiter._cleanup_old_recordsMsikkt''#288:: + +OC====:===F ++1#C((%%c****! ) )C#C(( t" # #d&= = =##))++44N  D'((3t/F/L+M+MMM)-8 - -Q',, NN'     r rrule_id attacker_ippendingreturnc  tj|jz |jkr|tj}||jz ||f}||jvrB|j|} fd|D}|r||j|<t |}n |j|=d}nd}||z }||jkr#|jdz} dd|d|d|d|jd | d fSd S) a Check if adding an incident would exceed rate limits. Args: rule_id: Rule identifier attacker_ip: IP address of the attacker pending: Incidents already accepted in the current batch but not recorded yet, so one file cannot exceed the limit on its own Returns: Tuple of (allowed: bool, reason: str) c g|] }|k| Sr#r#r$s rr(z8IncidentRateLimiter.check_rate_limit..r)r rrFzRate limit exceeded for rule z from IP z: /z within z minutes)TOK)rrrr>rrr1r) rr?r@rAr7r.r9r: recent_countwindow_minutesr's @rcheck_rate_limitz$IncidentRateLimiter.check_rate_limitysP 9;;* *T-B B B  % % ' ' 'ikkt'' $ $% % %,S1J====:===F !+1#C("6{{ ', L  43 3 3!-3N1G11#11$11'+'?11'111 zr ctj}||f}||jvr |g|j|<dS|j|}t||jkr|d||dS)z Record that an incident was added. Args: rule_id: Rule identifier attacker_ip: IP address rN)rrr1rpopr0)rr?r@r7r.r9s rrecord_incidentz#IncidentRateLimiter.record_incidentsikk $ d) ) )(+uD  $ $ $,S1J:$":::q!!!   c " " " " "r N)rr r)r) __name__ __module__ __qualname____doc__r4rr>strtupleboolrIrLr#r rrr+s.1#&"' (('*(!( ((((0* * * Z>?555),57:5 tSy 5555n#s#######r rc eZdZdZd dedzfdZ d!dededefd Z d!d eededefd Z e d e dee fd Z ejdZe de defdZdededzdedefdZde de dzfdZede defdZde dede dzfdZde defdZde defdZdededzfdZdeedededzdedef dZdS)"IncidentCollectorzM Collect and persist WordPress incidents from plugin incident files. N rate_limiterc||p t|_t|_t |_dS)z Initialize the incident collector. Args: rate_limiter: Optional rate limiter (creates default if not provided) N)rrVrparserset_failed)rrVs rrzIncidentCollector.__init__s6)A,?,A,A(** "% r Tsitedelete_after_processingrBcKg} t|d{V}td|||std|gS||}td|||std|gStdt ||||}|D]5}|||||d{V}||6n3#t$r&} t d|| Yd} ~ nd} ~ wwxYwt dt |||S) ab Collect incidents from a single WordPress site. Args: site: WordPress site to collect incidents from ruleset_version: Version of the ruleset being used delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects NzData directory for site %s: %sz)Data directory does not exist for site %sIncident files for site %s: %sz#No incident files found for site %sz%Found %d incident file(s) for site %sz*Error collecting incidents for site %s: %sz$Collected %d incident(s) for site %s) rr5debugexists_get_incident_filesr1_get_site_username _process_fileextend Exceptionerrorinfo) rr[r\collected_incidentsdata_dirincident_filesusername incident_filefile_incidentses rcollect_incidents_for_sitez,IncidentCollector.collect_incidents_for_sites !% )$////////H LL94 J J J??$$  H$OOO !55h??N LL0$   "  BDIII LL7N##    ..t44H!/ ; ; '+'9'9!+ (("""""" $**>:::: ;    LL<           2 # $ $    #"s&A!D/(AD/8A6D// E9EEsitescKg}|D]3}|||d{V}||4|r6tdt |t ||S)a Collect incidents from multiple WordPress sites. Args: sites: List of WordPress sites delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects Nz2Collected %d WordPress incident(s) from %d site(s))rordr5rgr1)rrpr\all_collected_incidentsr[site_incidentss rcollect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess#% ; ;D#'#B#B'$$N $ * *> : : : : "  KKD+,,E     '&r ric|dz }td|||r|std|gSg}|D]k} t j|}n#t$rY$wxYwtj |j r*| |r| |ltd|||S)z Get all incident files in the incidents directory. Args: data_dir: Path to the imunify-security data directory Returns: List of incident file paths incidentsz#Incidents directory for site %s: %sz.Incidents directory does not exist for site %sr^) r5r_r`is_diriterdiroslstatOSError stat_moduleS_ISREGst_mode_is_incident_filer0)clsri incidents_dirrjfsts rraz%IncidentCollector._get_incident_files<s0!;.  18]   ##%% ]-A-A-C-C  LL@(   I&&(( ) )A Xa[[    "2:.. )33H3H3K3K )%%a((( ,h   sB B"!B"z/^\d{4}-\d{2}-\d{2}-\d{2}(?:\.processing)?\.php$ file_pathcZt|j|jS)a3 Check if a file is an incident file based on naming pattern. Args: file_path: Path to the file to check Returns: True if file matches pattern yyyy-mm-dd-hh.php, with or without the suffix marking a batch left behind by an earlier cycle )rS _FILE_PATTERNmatchname)rrs rrz#IncidentCollector._is_incident_filees%C%++IN;;<<">" ## ' - m,,,& &   & 5   ]!3!3444 LLC"     IIIIII s1DAD$:DA$D E%A E E% E%rlc|jtr||rdS|S||jdt d tz}|r,||r||sdS||tj |dd|S)aMove the file out of the plugin's way before reading it. Returns None when an earlier batch is still pending under the aside name; that batch is processed in its own turn and the fresh file waits for the next cycle rather than overwriting it. N.phpF)follow_symlinks) rendswithPROCESSING_SUFFIX _quarantine with_namer1r`_pendingrenameryutime)rrlasides rrzIncidentCollector._take_asides   & &'8 9 9 ! .. t ''  ~#f++~ .1B B   <<>> dmmE22 ##E** tU### e4444 r rc tj|}n#t$rYdSwxYwtj|jo |jdkS)zWhether an aside still holds a batch waiting to be stored. Anything the site put there that is not a regular file is not one, and the rename replaces it. Fr)ryrzr{r|r}r~st_size)rrs rrzIncidentCollector._pendingsV %BB   55 "2:..A2:>As  %%pathsuffixc|j}tdfD]1}||r|dt| }n2|||z} ||n9#t $r,}td|j|Yd}~dSd}~wwxYw|j t||S)zGive a batch a name the collector will not pick up again. Renaming touches the name, never what it points at, so it stays safe in a directory the site owns. rNzFailed to retire %s: %s) rrrr1rrr{r5rfrZdiscardrQ)rrrstemknownretiredrns r_retirezIncidentCollector._retires y'0  E}}U## Ms5zzkM* ..//  KK     LL2DIq A A A44444  SYY'''sA22 B(> #'"3"D"D$g{%;<#E## NN= "Q&M#k!% (#x  !4iJ!!! $**=999';/000A50000C! 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6H#  KKG"    I-.ABB   +J 7 7 7 7    LL&'(("      .6^^-=-= H H ) "WkE5\\ H H!11';GGGG H  E   OO # $ $s: 6     sC"DDDE!!7F)N)T)rMrNrOrPrrrrSrrort classmethodrrarecompilerrrQrcr staticmethodrrrrrbdictrr#r rrUrUs ' '%84%? ' ' ' '")-?#?#?#"&?#  ?#?#?#?#H)-''F|'"&'  ''''B!4!DJ!!![!HBJ:M =$ =4 = = =[ =99* 9 "& 9  9999v$+2 B B B B B\ BD#$+,$2dt4 v #*    T:TT* T  T  TTTTTTr rU) rPloggingryrstatr|rrpathlibr collectionsrdefence360agent.model.wordpressrdefence360agent.wordpress.clir)defence360agent.wordpress.incident_parserr(defence360agent.model.wordpress_incidentrr r r getLoggerrMr5rrrrrrUr#r rrsw77  ######222222666666HHHHHH  8 $ $& #   Y#Y#Y#Y#Y#Y#Y#Y#xwwwwwwwwwwr defence360agent/wordpress/__pycache__/incident_collector.cpython-311.pyc0000644000000000000000000006003600000000000023360 0ustar r_j,SdZddlZddlZddlZddlZddlZddlZddlm Z ddl m Z ddl m Z ddlmZddlmZddlmZmZmZmZejeZd Zd Zd Zd ZGd dZGddZdS)z1Collector for WordPress CVE protection incidents.N)Path) defaultdict)WPSite) get_data_dir)IncidentFileParser)aggregate_incident_dictsbulk_create_wordpress_incidentsbuild_incident_dictcountry_readerz.processing.phpz .failed.phpz .stored.phpc peZdZdZ ddededefdZd Z dd ed ed edee effdZ d ed efdZ dS)IncidentRateLimitera Rate limiter to prevent DoS attacks via incident flooding. Implements per-rule-per-IP rate limiting as per spec: - Maximum 100 incidents for each rule from the same IP within 15 minutes Memory-optimized implementation with bounded entry count using LRU eviction. dr 'max_incidents_per_rule_per_iptime_window_secondsmax_unique_entriesc||_||_||_tt|_d|_tj|_dS)aI Initialize the rate limiter. Args: max_incidents_per_rule_per_ip: Max incidents per rule per IP (default: 100) time_window_seconds: Time window in seconds (default: 900 = 15 minutes) max_unique_entries: Max unique (rule_id, IP) combinations to track (default: 10000) <N) max_per_rule_per_ip time_windowrrlistincident_timescleanup_intervaltime last_cleanup)selfrrrs a/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_collector.py__init__zIncidentRateLimiter.__init__5sK$A ."4*$// " IKKc~ tj}||jz g}|jD]5\}} fd|D}|r ||j|< ||6|D] }|j|= t |j|jkrt|jd}tdt |jt|jdzz }|d|D] \}}|j|=t d|j|||_ dS)zHRemove records older than the time window and enforce max entries limit.c g|] }|k| Sr#.0tscutoffs r z.V===Rfbr c2|dr|ddndS)Nrr#)xs rz:IncidentRateLimiter._cleanup_old_records..es14ad1gg1r )keyr+g?NzARate limiter exceeded max entries (%d), removed %d oldest entries) rrritemsappendlenrsortedmaxintloggerwarningr) rnowkeys_to_deleter. timestampsrecententries_by_age num_to_remove_r's @r_cleanup_old_recordsz(IncidentRateLimiter._cleanup_old_recordsMsikkt''#288:: + +OC====:===F ++1#C((%%c****! ) )C#C(( t" # #d&= = =##))++44N  D'((3t/F/L+M+MMM)-8 - -Q',, NN'     r rrule_id attacker_ippendingreturnc  tj|jz |jkr|tj}||jz ||f}||jvrB|j|} fd|D}|r||j|<t |}n |j|=d}nd}||z }||jkr#|jdz} dd|d|d|d|jd | d fSd S) a Check if adding an incident would exceed rate limits. Args: rule_id: Rule identifier attacker_ip: IP address of the attacker pending: Incidents already accepted in the current batch but not recorded yet, so one file cannot exceed the limit on its own Returns: Tuple of (allowed: bool, reason: str) c g|] }|k| Sr#r#r$s rr(z8IncidentRateLimiter.check_rate_limit..r)r rrFzRate limit exceeded for rule z from IP z: /z within z minutes)TOK)rrrr>rrr1r) rr?r@rAr7r.r9r: recent_countwindow_minutesr's @rcheck_rate_limitz$IncidentRateLimiter.check_rate_limitysP 9;;* *T-B B B  % % ' ' 'ikkt'' $ $% % %,S1J====:===F !+1#C("6{{ ', L  43 3 3!-3N1G11#11$11'+'?11'111 zr ctj}||f}||jvr |g|j|<dS|j|}t||jkr|d||dS)z Record that an incident was added. Args: rule_id: Rule identifier attacker_ip: IP address rN)rrr1rpopr0)rr?r@r7r.r9s rrecord_incidentz#IncidentRateLimiter.record_incidentsikk $ d) ) )(+uD  $ $ $,S1J:$":::q!!!   c " " " " "r N)rr r)r) __name__ __module__ __qualname____doc__r4rr>strtupleboolrIrLr#r rrr+s.1#&"' (('*(!( ((((0* * * Z>?555),57:5 tSy 5555n#s#######r rc eZdZdZd dedzfdZ d!dededefd Z d!d eededefd Z e d e dee fd Z ejdZe de defdZdededzdedefdZde de dzfdZede defdZde dede dzfdZde defdZde defdZdededzfdZdeedededzdedef dZdS)"IncidentCollectorzM Collect and persist WordPress incidents from plugin incident files. N rate_limiterc||p t|_t|_t |_dS)z Initialize the incident collector. Args: rate_limiter: Optional rate limiter (creates default if not provided) N)rrVrparserset_failed)rrVs rrzIncidentCollector.__init__s6)A,?,A,A(** "% r Tsitedelete_after_processingrBcKg} t|d{V}td|||std|gS||}td|||std|gStdt ||||}|D]5}|||||d{V}||6n3#t$r&} t d|| Yd} ~ nd} ~ wwxYwt dt |||S) ab Collect incidents from a single WordPress site. Args: site: WordPress site to collect incidents from ruleset_version: Version of the ruleset being used delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects NzData directory for site %s: %sz)Data directory does not exist for site %sIncident files for site %s: %sz#No incident files found for site %sz%Found %d incident file(s) for site %sz*Error collecting incidents for site %s: %sz$Collected %d incident(s) for site %s) rr5debugexists_get_incident_filesr1_get_site_username _process_fileextend Exceptionerrorinfo) rr[r\collected_incidentsdata_dirincident_filesusername incident_filefile_incidentses rcollect_incidents_for_sitez,IncidentCollector.collect_incidents_for_sites !% )$////////H LL94 J J J??$$  H$OOO !55h??N LL0$   "  BDIII LL7N##    ..t44H!/ ; ; '+'9'9!+ (("""""" $**>:::: ;    LL<           2 # $ $    #"s&A!D/(AD/8A6D// E9EEsitescKg}|D]3}|||d{V}||4|r6tdt |t ||S)a Collect incidents from multiple WordPress sites. Args: sites: List of WordPress sites delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects Nz2Collected %d WordPress incident(s) from %d site(s))rordr5rgr1)rrpr\all_collected_incidentsr[site_incidentss rcollect_incidents_for_sitesz-IncidentCollector.collect_incidents_for_sitess#% ; ;D#'#B#B'$$N $ * *> : : : : "  KKD+,,E     '&r ric|dz }td|||r|std|gSg}|D]k} t j|}n#t$rY$wxYwtj |j r*| |r| |ltd|||S)z Get all incident files in the incidents directory. Args: data_dir: Path to the imunify-security data directory Returns: List of incident file paths incidentsz#Incidents directory for site %s: %sz.Incidents directory does not exist for site %sr^) r5r_r`is_diriterdiroslstatOSError stat_moduleS_ISREGst_mode_is_incident_filer0)clsri incidents_dirrjfsts rraz%IncidentCollector._get_incident_files<s0!;.  18]   ##%% ]-A-A-C-C  LL@(   I&&(( ) )A Xa[[    "2:.. )33H3H3K3K )%%a((( ,h   sB B"!B"z/^\d{4}-\d{2}-\d{2}-\d{2}(?:\.processing)?\.php$ file_pathcZt|j|jS)a3 Check if a file is an incident file based on naming pattern. Args: file_path: Path to the file to check Returns: True if file matches pattern yyyy-mm-dd-hh.php, with or without the suffix marking a batch left behind by an earlier cycle )rS _FILE_PATTERNmatchname)rrs rrz#IncidentCollector._is_incident_filees%C%++IN;;<<">" ## ' - m,,,& &   & 5   ]!3!3444 LLC"     IIIIII s1DAD$:DA$D E%A E E% E%rlc|jtr||rdS|S||jdt d tz}|r,||r||sdS||tj |dd|S)aMove the file out of the plugin's way before reading it. Returns None when an earlier batch is still pending under the aside name; that batch is processed in its own turn and the fresh file waits for the next cycle rather than overwriting it. N.phpF)follow_symlinks) rendswithPROCESSING_SUFFIX _quarantine with_namer1r`_pendingrenameryutime)rrlasides rrzIncidentCollector._take_asides   & &'8 9 9 ! .. t ''  ~#f++~ .1B B   <<>> dmmE22 ##E** tU### e4444 r rc tj|}n#t$rYdSwxYwtj|jo |jdkS)zWhether an aside still holds a batch waiting to be stored. Anything the site put there that is not a regular file is not one, and the rename replaces it. Fr)ryrzr{r|r}r~st_size)rrs rrzIncidentCollector._pendingsV %BB   55 "2:..A2:>As  %%pathsuffixc|j}tdfD]1}||r|dt| }n2|||z} ||n9#t $r,}td|j|Yd}~dSd}~wwxYw|j t||S)zGive a batch a name the collector will not pick up again. Renaming touches the name, never what it points at, so it stays safe in a directory the site owns. rNzFailed to retire %s: %s) rrrr1rrr{r5rfrZdiscardrQ)rrrstemknownretiredrns r_retirezIncidentCollector._retires y'0  E}}U## Ms5zzkM* ..//  KK     LL2DIq A A A44444  SYY'''sA22 B(> #'"3"D"D$g{%;<#E## NN= "Q&M#k!% (#x  !4iJ!!! $**=999';/000A50000C! 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6" 6H#  KKG"    I-.ABB   +J 7 7 7 7    LL&'(("      .6^^-=-= H H ) "WkE5\\ H H!11';GGGG H  E   OO # $ $s: 6     sC"DDDE!!7F)N)T)rMrNrOrPrrrrSrrort classmethodrrarecompilerrrQrcr staticmethodrrrrrbdictrr#r rrUrUs ' '%84%? ' ' ' '")-?#?#?#"&?#  ?#?#?#?#H)-''F|'"&'  ''''B!4!DJ!!![!HBJ:M =$ =4 = = =[ =99* 9 "& 9  9999v$+2 B B B B B\ BD#$+,$2dt4 v #*    T:TT* T  T  TTTTTTr rU) rPloggingryrstatr|rrpathlibr collectionsrdefence360agent.model.wordpressrdefence360agent.wordpress.clir)defence360agent.wordpress.incident_parserr(defence360agent.model.wordpress_incidentrr r r getLoggerrMr5rrrrrrUr#r rrsw77  ######222222666666HHHHHH  8 $ $& #   Y#Y#Y#Y#Y#Y#Y#Y#xwwwwwwwwwwr defence360agent/wordpress/__pycache__/incident_parser.cpython-311.opt-1.pyc0000644000000000000000000001545500000000000023632 0ustar r_jdZddlZddlZddlZddlZddlZddlmZddlm Z ej e Z GddZ dS)z+Parser for WordPress plugin incident files.N)Path) open_nofollowc eZdZdZededeedzfdZede de dededzfdZ ed e de dededzfd Z e d edefd ZdS) IncidentFileParsera' Parse incident files written by the WordPress plugin. These files have format: |sdS|dr#td||jdS|ds,td||j|dddS|dd}||||S)aD Process a single line from an incident file. Args: line: The line content (already stripped) line_num: Line number for logging file_path: Path to the file being processed Returns: Parsed incident dictionary or None if line should be skipped Nz     44444 s%A 0 check and blow up when the window is computed. r.Fr)floatKeyError TypeError ValueErrormathisfinite)rr.s r!r7z'IncidentFileParser._has_valid_timestamps\ x~&&BB)Z0   55 }R  +R!V+s 33)__name__ __module__ __qualname____doc__ classmethodrlistr5r"rintrr) staticmethodboolr7r+r!rrs   4 DJ,=   [ D'L'L"%'L26'L 'L'L'L['LR..*-.:>. ...[.` ,t , , , ,\ , , ,r+r)rFr/r2loggingrArpathlibrdefence360agent.utils.fd_opsr getLoggerrCrrrLr+r!rQs11  666666  8 $ $V,V,V,V,V,V,V,V,V,V,r+defence360agent/wordpress/__pycache__/incident_parser.cpython-311.pyc0000644000000000000000000001545500000000000022673 0ustar r_jdZddlZddlZddlZddlZddlZddlmZddlm Z ej e Z GddZ dS)z+Parser for WordPress plugin incident files.N)Path) open_nofollowc eZdZdZededeedzfdZede de dededzfdZ ed e de dededzfd Z e d edefd ZdS) IncidentFileParsera' Parse incident files written by the WordPress plugin. These files have format: |sdS|dr#td||jdS|ds,td||j|dddS|dd}||||S)aD Process a single line from an incident file. Args: line: The line content (already stripped) line_num: Line number for logging file_path: Path to the file being processed Returns: Parsed incident dictionary or None if line should be skipped Nz     44444 s%A 0 check and blow up when the window is computed. r.Fr)floatKeyError TypeError ValueErrormathisfinite)rr.s r!r7z'IncidentFileParser._has_valid_timestamps\ x~&&BB)Z0   55 }R  +R!V+s 33)__name__ __module__ __qualname____doc__ classmethodrlistr5r"rintrr) staticmethodboolr7r+r!rrs   4 DJ,=   [ D'L'L"%'L26'L 'L'L'L['LR..*-.:>. ...[.` ,t , , , ,\ , , ,r+r)rFr/r2loggingrArpathlibrdefence360agent.utils.fd_opsr getLoggerrCrrrLr+r!rQs11  666666  8 $ $V,V,V,V,V,V,V,V,V,V,r+defence360agent/wordpress/__pycache__/incident_sender.cpython-311.opt-1.pyc0000644000000000000000000003511300000000000023607 0ustar r_jY*dZddlZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZdd lmZdd lmZdd lmZmZejeZGd d ZdS)z3Send WordPress incidents to the correlation server.N)datetime)partial) monotonic)MappingProxyType)AnyMapping)SensorWordpressIncidentList) MessageSink) delivery_ack)get_unsent_wordpress_incidents#settle_wordpress_incidents_reportedc XeZdZdZdZdZddZdedeee ffdZ d ede efd Z d e dzdefd Zd e dzd e edefdZeifd e de edeeeffdZdedeeefddfdZdeddfdZdedeeefddfdZddZdeefdZdS)IncidentSenderai Send WordPress incidents to the correlation server. WordPress incidents are already in the Incident table (visible to UI). This class sends them to correlation via Reportable messages, which are handled by the SendToServer/SendToServerNATS/SendToServerFGW plugins. Those plugins queue a message rather than deliver it, and a send round can lose its batch or be force-cancelled mid-publish while the agent shuts down. Each incident therefore keeps a count of the occurrences the transport has not acknowledged, and every collection cycle sends whatever is still outstanding. ii,returnNci|_dSN) _inflightselfs ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_sender.py__init__zIncidentSender.__init__/s BDincidentctd||dpi}t|dpd}t |}|r't j|dnd}id|d|d|d d |d pd d |d d |dd|dd|dpdd|dpdd|dpdd|dpdd|dpdd|dpdd|dr|ddkndd|d pdd!|d"pdd#|||d$pd|d%pd|d&pd|d'pd|d(S))aJ Prepare an incident for sending to the correlation server. WordPress incidents use extra_info JSON field to store plugin-specific data. Args: incident: WordpressIncident dictionary (with extra_info populated) Returns: Dictionary formatted for correlation server z&Preparing incident for correlation: %s extra_info timestamprz%Y-%m-%ddt plugin_idpluginruleunknownnamemessage descriptionseverity attackers_ipabuserdomainretriesunsent_retriesuri request_uri user_agenthttp_user_agent http_methodrequest_methoduser_logged_intrueN file_path site_pathuserusernametagtargetslugversionmode)r:r;r<r=details) loggerinfogetfloatintr fromtimestampstrftime _build_tags)rrextratimestamp_valuerrs r!_prepare_incident_for_correlationz0IncidentSender._prepare_incident_for_correlation3s  z1IncidentSender.send_incidents..s7     2 28 < <   rcfg|].}|d|dpdf/S)idr+r,)rA)r`rs rraz1IncidentSender.send_incidents..sK   d##X\\2B%C%C%HqI   rci|] \}}||| Srr_)r` incident_id occurrencess r z1IncidentSender.send_incidents..s20 [#.  /..r) r?rVlendebugr@iterr batched _send_batch itertoolsislice)rrQr[correlation_batchpendingbatchs` rrZzIncidentSender.send_incidentssi < NNH I I I1 y>>Q   LL9 : : :1 8#i..       %      )      189JKK  E""4=4DU55          $%%%rroreportedcKtdt|tdtj|dt |}t jtjt| |gd  }||d<| || | |d{Vtd t||dS#t$r6}||td |d}~wwxYw) zSend a batch of incidents to correlation server. Uses SensorIncidentList Reportable message which is sent to correlation via the SendToServer/SendToServerNATS/SendToServerFGW plugins. z3Sending batch of %d incidents to correlation serverzCorrelation batch json: %s)indentT) sort_keys message_idNzCQueued %d wordpress incident(s) for correlation server (message %s)z"Failed to queue incident batch: %s)r?r@rhjsondumpsr hashlibsha1sorteditemsencode hexdigest_watchprocess_message Exception_unwatcherror)rrQrorrr$rwes rrlzIncidentSender._send_batchs  A ! " "     ( J( 3 3 3   ..?@@ \ J(())+<=   fhh   )++  !+  J))) &&w// / / / / / / / KK %&&          MM* % % % LL4     s2AD88 E81E33E8rwc|sdSt|}|t|jzf|j|<tj|t|j||dSr) dictr ACK_TIMEOUTrr registrywatchr _on_delivered)rrwrrs rrzIncidentSender._watchsv  F>>  KK$* *& z" ##  D& H = =     rcz|j|dtj|dSr)rpopr runwatch)rrws rrzIncidentSender._unwatchs7 :t,,,%%j11111rc|j|dt|}td|dS)Nz.s2   ) MQ3 rz]No delivery confirmation for %d wordpress incident(s) (message %s) in %ds, sending them againN) rrr}rr rrr?rVrhr)rexpiredrwrrrrs @rrWzIncidentSender._expire_inflightskk    -1^-A-A-C-C    "  J.,,Z88KHa  ! ) )* 5 5 5 NN;H        rcHd|jDS)Nc"h|] \}}|D]}| Sr_r_)r`rrrres r z/IncidentSender._inflight_ids..&sC   !'       r)rvaluesrs rrYzIncidentSender._inflight_ids%s1  #~4466    r)rN)__name__ __module__ __qualname____doc__rXrrrstrrrIlistrFr rCr\rZrrrlrrrrWsetrYr_rrrrs   #KEEEE8 8 c3h8 8 8 8 t  $s)    :t1C:::::(2&$&2&37:2& 2&2&2&2&p'7&6r&:&: 222 :2#s(# 2222h    S0A  d     23242222    tCH~  $     ( s3x      rr)rrzrmrxloggingr functoolsrtimertypesrtypingrr"defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr defence360agent.internalsr (defence360agent.model.wordpress_incidentr r getLoggerrr?rr_rrrs@99 """"""JJJJJJ999999222222  8 $ $R R R R R R R R R R rdefence360agent/wordpress/__pycache__/incident_sender.cpython-311.pyc0000644000000000000000000003511300000000000022650 0ustar r_jY*dZddlZddlZddlZddlZddlmZddlmZddlm Z ddl m Z ddl m Z mZddlmZdd lmZdd lmZdd lmZmZejeZGd d ZdS)z3Send WordPress incidents to the correlation server.N)datetime)partial) monotonic)MappingProxyType)AnyMapping)SensorWordpressIncidentList) MessageSink) delivery_ack)get_unsent_wordpress_incidents#settle_wordpress_incidents_reportedc XeZdZdZdZdZddZdedeee ffdZ d ede efd Z d e dzdefd Zd e dzd e edefdZeifd e de edeeeffdZdedeeefddfdZdeddfdZdedeeefddfdZddZdeefdZdS)IncidentSenderai Send WordPress incidents to the correlation server. WordPress incidents are already in the Incident table (visible to UI). This class sends them to correlation via Reportable messages, which are handled by the SendToServer/SendToServerNATS/SendToServerFGW plugins. Those plugins queue a message rather than deliver it, and a send round can lose its batch or be force-cancelled mid-publish while the agent shuts down. Each incident therefore keeps a count of the occurrences the transport has not acknowledged, and every collection cycle sends whatever is still outstanding. ii,returnNci|_dSN) _inflightselfs ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/incident_sender.py__init__zIncidentSender.__init__/s BDincidentctd||dpi}t|dpd}t |}|r't j|dnd}id|d|d|d d |d pd d |d d |dd|dd|dpdd|dpdd|dpdd|dpdd|dpdd|dpdd|dr|ddkndd|d pdd!|d"pdd#|||d$pd|d%pd|d&pd|d'pd|d(S))aJ Prepare an incident for sending to the correlation server. WordPress incidents use extra_info JSON field to store plugin-specific data. Args: incident: WordpressIncident dictionary (with extra_info populated) Returns: Dictionary formatted for correlation server z&Preparing incident for correlation: %s extra_info timestamprz%Y-%m-%ddt plugin_idpluginruleunknownnamemessage descriptionseverity attackers_ipabuserdomainretriesunsent_retriesuri request_uri user_agenthttp_user_agent http_methodrequest_methoduser_logged_intrueN file_path site_pathuserusernametagtargetslugversionmode)r:r;r<r=details) loggerinfogetfloatintr fromtimestampstrftime _build_tags)rrextratimestamp_valuerrs r!_prepare_incident_for_correlationz0IncidentSender._prepare_incident_for_correlation3s  z1IncidentSender.send_incidents..s7     2 28 < <   rcfg|].}|d|dpdf/S)idr+r,)rA)r`rs rraz1IncidentSender.send_incidents..sK   d##X\\2B%C%C%HqI   rci|] \}}||| Srr_)r` incident_id occurrencess r z1IncidentSender.send_incidents..s20 [#.  /..r) r?rVlendebugr@iterr batched _send_batch itertoolsislice)rrQr[correlation_batchpendingbatchs` rrZzIncidentSender.send_incidentssi < NNH I I I1 y>>Q   LL9 : : :1 8#i..       %      )      189JKK  E""4=4DU55          $%%%rroreportedcKtdt|tdtj|dt |}t jtjt| |gd  }||d<| || | |d{Vtd t||dS#t$r6}||td |d}~wwxYw) zSend a batch of incidents to correlation server. Uses SensorIncidentList Reportable message which is sent to correlation via the SendToServer/SendToServerNATS/SendToServerFGW plugins. z3Sending batch of %d incidents to correlation serverzCorrelation batch json: %s)indentT) sort_keys message_idNzCQueued %d wordpress incident(s) for correlation server (message %s)z"Failed to queue incident batch: %s)r?r@rhjsondumpsr hashlibsha1sorteditemsencode hexdigest_watchprocess_message Exception_unwatcherror)rrQrorrr$rwes rrlzIncidentSender._send_batchs  A ! " "     ( J( 3 3 3   ..?@@ \ J(())+<=   fhh   )++  !+  J))) &&w// / / / / / / / KK %&&          MM* % % % LL4     s2AD88 E81E33E8rwc|sdSt|}|t|jzf|j|<tj|t|j||dSr) dictr ACK_TIMEOUTrr registrywatchr _on_delivered)rrwrrs rrzIncidentSender._watchsv  F>>  KK$* *& z" ##  D& H = =     rcz|j|dtj|dSr)rpopr runwatch)rrws rrzIncidentSender._unwatchs7 :t,,,%%j11111rc|j|dt|}td|dS)Nz.s2   ) MQ3 rz]No delivery confirmation for %d wordpress incident(s) (message %s) in %ds, sending them againN) rrr}rr rrr?rVrhr)rexpiredrwrrrrs @rrWzIncidentSender._expire_inflightskk    -1^-A-A-C-C    "  J.,,Z88KHa  ! ) )* 5 5 5 NN;H        rcHd|jDS)Nc"h|] \}}|D]}| Sr_r_)r`rrrres r z/IncidentSender._inflight_ids..&sC   !'       r)rvaluesrs rrYzIncidentSender._inflight_ids%s1  #~4466    r)rN)__name__ __module__ __qualname____doc__rXrrrstrrrIlistrFr rCr\rZrrrlrrrrWsetrYr_rrrrs   #KEEEE8 8 c3h8 8 8 8 t  $s)    :t1C:::::(2&$&2&37:2& 2&2&2&2&p'7&6r&:&: 222 :2#s(# 2222h    S0A  d     23242222    tCH~  $     ( s3x      rr)rrzrmrxloggingr functoolsrtimertypesrtypingrr"defence360agent.contracts.messagesr !defence360agent.contracts.pluginsr defence360agent.internalsr (defence360agent.model.wordpress_incidentr r getLoggerrr?rr_rrrs@99 """"""JJJJJJ999999222222  8 $ $R R R R R R R R R R rdefence360agent/wordpress/__pycache__/plugin.cpython-311.opt-1.pyc0000644000000000000000000026057200000000000021761 0ustar r_jI( ddlZddlZddlZddlZddlZddlZddlmZddlm Z m Z ddl m Z ddl mZddlmZddlmZddlmZmZmZmZmZdd lmZmZdd lmZdd lm Z dd l!m"Z"m#Z#m$Z$m%Z%dd lm&Z&ddl'm(Z(ddl)m*Z*m+Z+ddl,m-Z-m.Z.ddl/m0Z0ddl1m2Z2m3Z3ddl4m5Z5ddl6m7Z7m8Z8m9Z9m:Z:m;Z;mZ>m?Z?m@Z@mAZAddlBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOddlPmQZQejReSZTedZUdZVejWZXdaYdZZdZ[de\fdZ]de\fdZ^de\fdZ_de\fdZ`deafd Zbd!Zcd"Zdd#Zedefe\e\e\ffd$Zgd%eadefe\eaffd&Zhd%eade\fd'Zid%eade\fd(Zjd%eade\fd)Zked*Zld%ead+efd,Zmd-edendzfd.Zod/Zpd0end1ejqdenfd2Zrd1ejqd+efd3Zsd4ejtfd5Zudvd7evfd8Zwd9Zxd:Zyd;Zzdeafd<Z{d=Z|d>e.fd?Z}d>e.de~fd@ZdAZdBZ dwdCee.fdDZdEeve.fdFZdddGd>e.dHeadIend1ejqdzdJedzddf dKZdddGd>e.dLend1ejqdzdJedzfdMZdddGd>e.dNend1ejqdzdJedzddf dOZdEeve.de~fdPZd>e.d1ejqdQeadRedSeddf dTZ dxdEeve.dUe e.ejqeege dfdVeadWeadXe\ddf dYZdwdQeaddfdZZd-ed[e\ddfd\ZejWZdadyd]Zdyd^Zd_eaddfd`ZdEeve.ddfdaZd%eaddfdbZd%eaddfdcZdyddZdydeZdfeadgedeafdhZd>e.d1ejqdgedRedSeddf diZ dzdjeveadzddfdkZdwdlZdmZd>e.de\fdnZd>e.doe~de\fdpZdqZGdrdsZGdtdueZdS){N) defaultdict) AwaitableCallable) LooseVersion)cache)Path) inactivity)MalwareScanScheduleInterval SystemConfigANTIVIRUS_MODEUserTypechoose_value_from_config)IndexWP_RULES) log_message)importer)open_dir_no_symlinks open_nofollow rmtree_fdsafe_dir) Wordpress) hosting_panel)get_wp_rules_dataget_wp_ruleset_version) WordpressSiteWPSite)WPDisabledRule)cli telemetry)PLUGIN_VERSION_FILE) _validate_presetcalculate_next_scan_timestamp$clear_get_cagefs_enabled_users_cacheensure_site_data_directoryformat_php_with_embedded_jsonget_imunify_package_versions get_last_scanget_malware_historyprepare_plugin_configprepare_scan_data!write_plugin_data_file_atomically) clear_manually_deleted_flag delete_siteget_installed_sites_by_domainsget_outdated_sitesget_sites_for_userget_sites_to_adoptget_sites_to_install%get_sites_to_mark_as_manually_deletedget_installed_sitesinsert_installed_sitesmark_site_as_manually_deletedupdate_site_identityupdate_site_version)setup_site_authenticationc0tjdddS)Nz(imav.malwarelib.plugins.schedule_watcherget_user_schedule_config)modulenamedefault)rgetU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/plugin.py_get_user_schedule_config_imavrCKs$ <9 '   rATFbalancedreturncd ttjS#t$r tcYSwxYwN)boolr WAF_ENABLEDKeyError_LEGACY_WAF_FALLBACKr@rArB_get_global_waf_enabledrLc?$I)*** $$$####$ //cd ttjS#t$r tcYSwxYwrG)rHr WAF_DEFAULTrJrKr@rArB_get_waf_defaultrQjrMrNcX ttjS#t$rYdSwxYw)NF)rHrSECURITY_PLUGIN_ENABLEDrJr@rArB_get_security_plugin_enabledrTqs: I5666 uus  ))cd ttjS#t$r tcYSwxYw)uSRead WORDPRESS.ai_bot_protection from config, defaulting to False. Returns _AI_BOT_PROTECTION_DEFAULT when the config key is missing — e.g. the ai_bot_protection field hasn't rolled out to this install's imunify360 yet, or a sibling package is still on an older schema. Keeps the feature off in all ambiguous cases. )rHrAI_BOT_PROTECTIONrJ_AI_BOT_PROTECTION_DEFAULTr@rArB_get_global_ai_bot_protectionrX|s?*I/000 ***))))*rNcj tj}n#t$r tcYSwxYwt |S)uRead WORDPRESS.ai_bot_protection_preset from config, defaulting to "balanced". Two layers of safety: KeyError on a missing key (older schema, agent upgrade in progress) and _validate_preset() on the value itself (hand-edited override file, future preset rolled in via a sibling package this version doesn't recognise). Both fall back to the same canonical default so all layers — schema, agent, plugin — agree. )rAI_BOT_PROTECTION_PRESETrJ!_AI_BOT_PROTECTION_PRESET_DEFAULTr!)raws rB$_get_global_ai_bot_protection_presetr]sF10 11100001 C  s ##r>overridezglobal kill switchcTtttfS)aRead the three server-wide WAF flags in one call. Returns (security_plugin_enabled, global_waf_enabled, waf_default), each guarded against a missing config key (schema version skew during an agent/imunify-antivirus upgrade) the same way the individual accessors are. )rTrLrQr@rArBwaf_global_snapshotr`s) %&&!! rAusernamects dtfS tdd|\}}n%#t$rt t fcYSwxYw|t jkrt t fSt|tfS)NF WORDPRESS waf_enabledra) rLWAF_SOURCE_KILL_SWITCHrrJrQWAF_SOURCE_DEFAULTr ROOTrHWAF_SOURCE_OVERRIDE)ravaluesources rB#waf_status_and_source_for_user_syncrls " $ $-,,,60    vv 666!!#555556 !!#555 ;;+ ++s/AAc*t|\}}|SrG)rl)raenabled_s rB_is_waf_enabled_for_user_syncrps4X>>JGQ NrAcpKtj}|dt|d{VS)u3Async wrapper — runs config file I/O in executor.N)asyncioget_running_looprun_in_executorrp)raloops rBis_waf_enabled_for_userrvsR  # % %D%% +X      rAcr tdd|\}}n#t$rYdSwxYw|tjkS)NrcrdreF)rrJr rh)rarorks rB$_user_has_explicit_waf_override_syncrxsY,    66 uu X] ""s  &&zD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3 admin_configct}|*tdtjdddfS|||S)z Get user-specific schedule configuration with lazy import fallback. Returns default values if imav.malwarelib is not available. Nz@imav.malwarelib not available, returning default schedule configr)rCloggerdebugIntervalNONE)raryr;s rB_get_user_schedule_configrsU >??' N   }aA%% # #Hl ; ;;rAindexct|}|dStr|D] \}}d|d< tt jr fd|D}|S)uI Retrieve WordPress rules with ANTIVIRUS_MODE handling and global disable filtering. In ANTIVIRUS_MODE, all rules are set to monitoring mode ("pass"). Globally disabled rules are filtered out entirely — they should not appear in rules.php. Domain-specific disables are handled separately via disabled-rules.php. Args: index: The Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data with mode adjusted for ANTIVIRUS_MODE and globally disabled rules removed, or None if rules cannot be loaded. Npassmodec$i|] \}}|v || Sr@r@).0cveparamsglobally_disableds rB z-get_updated_wp_rules_data..s5   V+++ +++rA)rr itemssetrget_global_disabled)r rules_datarrrs @rBget_updated_wp_rules_datars #5))Jt$%++-- $ $KC#F6NNN>@@AA     )//11   rAcHttjdS)z#Clear all WordPress-related caches.N)r#rclear_get_content_dir_cacher@rArB clear_cachesr s#(***#%%%%%rAr user_infoct|}d|D}|D]Dfd|D}|r1t|t}||E|S)Nci|]}|gSr@r@)rpaths rBrzsite_search..s . . .4dB . . .rAc,g|]}||Sr@r@)rritemmatchers rB zsite_search..s*MMM4t9L9LM$MMMrA)key)r0maxlenappend)rrr user_sitesresultmatching_sitesmost_specific_siters ` @rB site_searchrs#I..J . .: . . .F44MMMMM:MMM  4!$^!=!=!=  % & - -d 3 3 3 MrAc:Kt||jd{V}|dd}t|j|\}}}}d} |tjkrt ||||} t|j} t| |d} || | fS)N scan_datecP|ddko|d|S)N resource_typefile) startswith)rrs rBz)_get_scan_data_for_user..>s-40F:* L # #D ) )rA) r'pw_namer?rr~rr"r(r) sinkrry last_scanlast_scan_timeintervalhour day_of_month day_of_weeknext_scan_timemalware_historymalware_by_sites rB_get_scan_data_for_userr"s$D)*;<<<<<<<? ::rA semaphorecK|4d{V |d{Vn4#t$r'}td|Yd}~nd}~wwxYwdddd{VdS#1d{VswxYwYdS)NzTelemetry task failed: ) Exceptionr|error)corores rB_send_telemetry_taskrEs+88888888 8JJJJJJJJ 8 8 8 LL6166 7 7 7 7 7 7 7 7 8888888888888888888888888888888s5AA AAAAA A'*A' coroutinescK|sdStj|fd|D} tj|d{VdS#t$r(}td|Yd}~dSd}~wwxYw)zK Process a list of telemetry coroutines with a concurrency limit.s NcTg|]$}tjt|%Sr@)rr create_taskr)rrrs rBrz+process_telemetry_tasks..Us?     0yAABB   rAzSome telemetry tasks failed: )rr Semaphoregatherrr|r)r concurrencytasksrrs @rBprocess_telemetry_tasksrMs !+..I       E :ne$$$$$$$$$$ ::: 8Q88999999999:sA A3 A..A3cpK ttd}|d{Vt|}n3#t$r&}t d|Yd}~dSd}~wwxYw|st ddSt|}||d}t|S)z Load WordPress rules from the index and format them as PHP. Returns: str or None: PHP-formatted rules data, or None if rules could not be loaded. F)integrity_checkNz>Failed to load wp-rules index: %s, skipping rules installationz>L9;;;;;;;;H(--M& 5 5dh'..t4444,1133k k  U  # S 1 1I(0HH LLE HHHH 2)\ ""#!6h ? ? !UUD3D$????????! R%(%?%E%EEEEEEE%"KK Et%%6**$ +%- %%% *D )**$$$$$$ 4 %&/%- 8 )&/%- "/555555555 D)))),(>t(D(D"D"D"D"D"D"D"/3|,0g>>>$(#:#:7#C#CD,8 ',, ,-= > >,?L ,22 ) 4)-,8)B(?(?-A)-,3 !" !" !"   % I !aUn KK@G     %    KK+G           LL?      */:: : : : : : : : :)/:: : : : : : : : : :eR;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;s9Q;>PPQP33QQ QrcKtj|d{V} t|}nn#t$ra}|jtjkrYd}~dS|jtjtjfvr!t d|Yd}~dSd}~wwxYw t|j 5} tj t|d{Vtj|d}n#tj|d}wxYwtj|j|ddddS#1swxYwYdS#t&$r|dkrtj|wxYw)Nz/Skipping rmtree: data directory %s is a symlinkdir_fdr)r get_data_dirrOSErrorerrnoENOENTELOOPENOTDIRr|rrparentrr to_threadrosclosermdirr= BaseException)rrr exc parent_fds rBdelete_plugin_filesrIs%d++++++++H %h//  9 $ $ FFFFF 9em4 4 4 NNA8    FFFFF   ho & & 6) ' 6:::::::::       HX]9 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6  Q;; HV    sh. BB:BBBD:1D-3 C*D-*DD- D:-D11D:4D15D::&E cK tj|d{V}|s+t|tj||d{VdStj|d{V}tj|d{Vt |d{Vt|}|tj |d|||S#t$r'}t d||Yd}~dSd}~wwxYw)a7 Remove the imunify-security plugin from a single site, including all cleanup and telemetry. Returns the number of affected sites (should be 1 if deletion was successful). This function is intended to be protected with asyncio.shield to ensure it completes even if the parent task is cancelled. Nruninstalled_by_imunifyrz"Failed to remove plugin from %s %s)ris_plugin_installedprocess_manually_deleted_plugintimerplugin_uninstallrr-rrrrr|r)rrr is_installedraffectedrs rBremove_from_single_siter$hsz # 4T::::::::  1dikk4       1.t44444444"4((((((((("$'''''''''t$$   .         94GGGqqqqqsAC BC DC<<Dc Ktdg}d}tjd5 t t }|D]m} |tjt|||d{Vz }0#tj $r,td|t|YjwxYwn.#t$r!}t d|d}~wwxYw td|t|d{Vn5#td|t|d{VwxYw ddddS#1swxYwYdS)zHRemove the imunify-security plugin from all sites where it is installed.z#Deleting imunify-security wp pluginrzwp-plugin-removalNz_Deleting imunify-security wp plugin was cancelled. Plugin was deleted from %d sites (out of %d)z)Error occurred during plugin deleting. %sz0Removed imunify-security wp plugin from %s sites)r|rr rrrr4rrshieldr$rrrrr)rrr# to_removerrs rBremove_all_installedr(s5 KK5666OH    2 3 3;; ; NNN+--I!   gn/dOLL''!!!!!!HH-KKH I      LLDe L L L    KKB    */:: : : : : : : : : KKB    */:: : : : : : : : : :9;;;;;;;;;;;;;;;;;;slE2 C#,BC8C C C  CD/ C:C55C::D/>1E2/2E!!E22E69E6cK t||t|d{V|tj|d||jdS#t $r'}td||Yd}~dSd}~wwxYw)a Process the manually deleted plugin for a single site. Args: site: The site to process. now: The current time. sink: The telemetry/event sink. telemetry_coros: The list of telemetry coroutines to add the event to. The process includes: - marking the site as manually deleted in the database - removing plugin data files - sending telemetry for manual removal Nremoved_by_userrz>Failed to process manually deleted plugin for site=%s error=%s) r6rrrrrrr|r)rnowrrrs rBrrs %dC000"$'''''''''   '              L            sAA B %BB freshly_installed_sitescdKg} t|}|r0tj}|D]}t||||d{Vn2#t$r%}td|Yd}~nd}~wwxYw|rt |d{VdSdS#|rt |d{VwwxYw)a> Tidy up sites that have been manually deleted by the user. Args: sink: The telemetry/event sink. freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Nz&Error occurred during site tidy up. %s)r3r rrr|rr)rr,rto_mark_as_manually_removedr+rrs rBtidy_up_manually_deletedr/s>O;&K #' ' # ' )++C3  5#t_ FFF =uEEEEEEEEF  ;)/:: : : : : : : : : : ; ;? ;)/:: : : : : : : : : ;s0AAB A7A2-B2A77BB/rc NK|sdSt}td{V}tt}|D]"}||j|#|D]-\}} tj|}|j }n3#t$r&} t d|| Yd} ~ Nd} ~ wwxYwt|||d{V\} } } t|} |D]}t||d{Vr t!| | ||| |}t#||d{V}t%||||d{Vt'|| ||d{Vx#t$r&} t d|| Yd} ~ d} ~ wwxYw/dS)Nrrrz.Failed to update site data on site=%s error=%s)r r&rrrrrrrrrr|rrr)rr*r$rr)rrryrrrrrrarrrrrrrs rBupdate_data_on_sitesr1s  >>L133333333H %%M--dh&&t,,,,$))++22 U  S))I (HH    LL=    HHHH  *$ <HH H H H H H H    -h77   D+D$77777777  -""#%  "/``. A caller writing several files into one site's directory can resolve ``user_info`` and ``data_dir`` once and pass them in, so the owner lookup and directory-ensure are not repeated per file. Nrgid)rrrr$r%r+pw_gid)rr2r3rr php_contents rB_write_json_php_data_filer9BsL** 3D)DDDDDDDD/55K%8hIKt|d|||d{VdS)N scan_data.phprr9)rrrrs rBrrZsV $     rArc>Kt|d|||d{VdS)z Write plugin_config.php for a single WordPress site. Separate file from scan_data.php so a config toggle doesn't force rewriting the malware list, and so the mu-plugin hot path loads only what it needs per request. plugin_config.phprNr<)rrrrs rBrrjsV $     rAc&K|sdSd}tt}|D]"}||j|#|D]\}} t j|}|j}n3#t$r&}t d||Yd}~Md}~wwxYwt|} |D]S} t|| |d{V|dz }!#t$r&}t d||Yd}~Ld}~wwxYw|S)us Rewrite plugin_config.php on every managed site in one pass. Used by the ConfigUpdate handler that reacts to WORDPRESS.ai_bot_protection toggles. Writes only plugin_config.php — scan_data.php is untouched, so a toggle doesn't churn the (potentially large) malware payload or wait on a scan cycle. No sink is needed: unlike update_data_on_sites we emit no telemetry here — the per-site write loop just needs local file I/O plus the process-level logger for errors. Returns the number of sites successfully updated so the caller can decide whether to advance its cached state. rrN)rr{z6Failed to update plugin_config.php on site=%s error=%s) rrrrrrrrrr|rr)r) rrrrrrrrarrs rBupdate_plugin_config_on_sitesr@s qG .9->->M--dh&&t,,,,(..00Z  S))I (HH    LL=    HHHH  .h77   D /-91     L   Ns0A88 B(B##B(?C D 'DD  wp_rules_phprfailedcK|j} t||d{V}|dz }t|||j|||t d|jdS#t$rA}||t d|j|Yd}~dSd}~wwxYw)a= Deploy wp-rules to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd wp_rules_php: Formatted PHP rules content updated: Set to add site to if successful failed: Set to add site to if failed N rules.phpr5zUpdated wp-rules for site %sz)Failed to update wp-rules for site %s: %s) r7r$r+rrr|rdocrootrr) rrrArrBr6r rules_pathrs rBupdate_wp_rules_for_siterGs"  C 3D)DDDDDDDD + )  $(      D 2DLAAAAA     4 7 L           sA(A55 C?6B;;C make_task task_name fingerprintskip_waf_disabledc Kt}t}tj|5 t j}t t } |D]"} | | j| #g} | D]+\} } tj | }|j }nW#t$rJ}td|t| | |ddd|| D]} || Yd}~rd}~wwxYw|rr t#|d{V}n/#t$r"t$d|d d}YnwxYw|s*t$d |t| | D]:} t+|| d{Vr| || |||;-d }t-d t| |D]&}| |||z}t/j|d did{V't j|z }t$d|t|t||nh#t.j$r,t$d|t|Yn.t$r"}t$d||d}~wwxYwddddS#1swxYwYdS)a6 Run a per-site async deployment over a list of WordPress sites. Groups sites by user, resolves UIDs, then runs tasks concurrently in batches. Args: sites: WordPress sites to deploy to make_task: Callable that creates a coroutine for one site. Signature: (site, user_info, updated_set, failed_set) -> awaitable task_name: Human-readable name for logging and inactivity tracking fingerprint: Sentry fingerprint for user-lookup failures sink: Optional telemetry sink for remove_site_if_missing zwSkipping {task} update for {count} site(s) belonging to user {user} because username retrieval failed. Reason: {reason})rcountuserreasonr wordpress format_argslevel componentrJNBCould not check WAF status for user %s, proceeding with deploymentTexc_infoz-WAF disabled for user %s, skipping %d site(s)rrreturn_exceptionsz@%s deployment complete. Updated: %d, Failed: %d, Duration: %.2fsz-%s deployment was cancelled. Updated %d sitesz-Error occurred during %s deployment. error=%s)rr rrr rrrrrrrrrrrrrvr|rrrrangerrrrr)rrHrIrJrKrrrB start_timerrrrrrrarrdmax_concurrentibatchelapseds rB_deploy_to_sitesr_s0eeG UUF    y ) )SSR J'--M 5 5dh'..t4444E#0#6#6#8#8- N- NZ # S 1 1I(0HH >%.%(__$'&+ %% ("-$/    !+)) 4((((HHHH#&%! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +'! K$ OO !&NND3D$????????! LL4GV!L!LMMMMN  N1c%jj.99 E Ea!n"445neDtDDDDDDDDDDikkJ.G KK#G F      %    KK?G           LL?      [SSSSSSSSSSSSSSSSSSsKA(I%)CI% DADI%DI%D54I%5)E!I% E!!DI%$K%8K K K (KK  KK!KcKtt}|stddSfd}t ||ddd|d{VdS)zHDeploy pre-formatted wp-rules PHP content to all active WordPress sites.zNo active WordPress sites foundNc*t||||SrG)rG)rrrrBrAs rBrHz'_deploy_wp_rules_php..make_taskYs ' )\7F   rAzwp-ruleszwp-rules-update-skip-userTrIrJrKr)rr4r|r}r_)rArinstalled_sitesrHs` rB_deploy_wp_rules_phprdPsNNN)++O  6777     /     rA is_updatedcKtjstddS|stddStdt |}|stddSt |}||d}t|}t|d{VdS)z Hook that runs when wp-rules files are updated. Extracts wp-rules.yaml from wp-rules.zip and deploys to all active WordPress sites. Args: index: Index object for wp-rules is_updated: Whether files were actually updated zCwordpress security plugin not enabled, skipping wp-rules deploymentNz)wp-rules not updated, skipping deploymentz/Starting wp-rules deployment to WordPress sitesz,No valid wp-rules found, skipping deploymentr) rrSr|rrrrr%rd)rrerrrrAs rBupdate_wp_rules_on_sitesrghs  ,        ?@@@ KKABBB-e44M  CDDD.e44#L1>>L | , ,,,,,,,,,,rAc`KtjstddStrdatddSt4d{V datdtd{V}|s.td dddd{VdSt|d{Vt sntd dddd{VdS#1d{VswxYwYdS) aN Re-deploy rules.php to all WordPress sites. Used when globally disabled rules change, requiring rules.php to be regenerated with updated rule filtering. Uses a coalescing lock: if a redeployment is already running, the request is merged into the current run rather than starting a duplicate deployment. zEwordpress security plugin not enabled, skipping wp-rules redeploymentNTz5wp-rules redeployment already in progress, coalescingFz6Starting wp-rules redeployment (global disable change)z(Could not load wp-rules for redeploymentz4Re-running wp-rules redeployment (coalesced request)) rrSr|r_redeploy_rules_php_locklocked_redeploy_rules_php_pendingrrrd)rAs rBredeploy_rules_phprlsS  ,      &&((&*# KLLL'PPPPPPPP P*/ ' KKH   "3!4!4444444L IJJJPPPPPPPPPPPPPP'|44 4 4 4 4 4 4 4.  KKN O O O! P PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPs/AD8D D'*D'cZKtd{Vtd{VdS)a9Global WAF turn-on: deploy rules.php and disabled-rules.php (stamping disabled_rules_sync_ts) to all sites, matching install-with-WAF-on. Wraps rather than extends redeploy_rules_php, which is also the global-rule-change path where restamping sync_ts would skip unconsumed changelog actions. N)rlupdate_disabled_rules_on_sitesr@rArBredeploy_waf_for_all_sitesrosJ    ( * **********rArEct t|}n#t$rYdSt$rg}|jtjtjfvr!t d|Yd}~dSt d||Yd}~dSd}~wwxYw dD]t} tj ||t d||6#t$rYBt$r'}t d|||Yd}~md}~wwxYw tj |dS#tj |wxYw)z9Remove WAF files from data_dir via a symlink-safe dir fd.Nz3Skipping WAF file removal: data dir %s is a symlinkz"Failed to open data dir for %s: %s)rDdisabled-rules.phpr z!Removed %s from %s (WAF disabled)zFailed to remove %s from %s: %s) rFileNotFoundErrorrrrrr|rrrremoverr)rrEr rr2rs rB_remove_waf_files_for_dirrts %h//   9em4 4 4 NNE    FFFFF 97CHHH;  H  (62222 77%       5x!   sa B B:B (B  BD!2C  D! DD! D D=D!DD!!D7cK|D]|} tj|d{V}n8#t$r+}td|j|Yd}~Nd}~wwxYwt jt||jd{V}dS)aRemove WAF files (rules.php, disabled-rules.php) from the given sites. Deletion goes through open_dir_no_symlinks + dir_fd so a site owner cannot redirect the root agent's removal via a symlinked data dir, the same symlink-safe pattern as delete_plugin_files. Nz%Failed to resolve data dir for %s: %s) rr rr|rrErrrrt)rrrrs rB_remove_waf_files_from_sitesrvs     -d33333333HH    LL7q    HHHH    %x             s# A!AAc t KtjsdStj} |dt j|d{V n,#t$rt d|YdSwxYw|dtd{V} fd|D}|sdSt}t}td{V}|r|D]}t| |||d{Vnt dtj}t} t} |D]}t| || | d{Vtd|t#|t#|t#| t#| dS)u\Deploy rules.php and disabled-rules.php for a user's sites (WAF turn-on). Caller must have confirmed WAF is enabled for the user. disabled-rules.php is deployed even if the ruleset fails to load, because it stamps disabled_rules_sync_ts — matching install, which writes it whenever WAF is on regardless of rules.php content. Nz.User %s not found, skipping WAF rules redeployc4g|]}|jjk|Sr@rpw_uidrsrs rBrz)redeploy_waf_for_user..(@@@aey/?&?&?!&?&?&?rAz)Could not load wp-rules for user redeployz[Redeployed WAF artifacts for user %s: rules %d ok/%d failed, disabled-rules %d ok/%d failed)rrSrrrsrtrgetpwnamrJr|rr4rrrGr update_disabled_rules_for_siterr) rarurrrrBrArdisabled_rules_ts dr_updated dr_failedrs @rBredeploy_waf_for_userrsE  ,  # % %D..tS\8LLLLLLLL  .Dr}rA) rrrsrtrr~rJr|rr4rv)rarurrrs @rBremove_waf_rules_for_userr8s  # % %D..tS\8LLLLLLLL  ;X      &&t-@AA A A A A A AE@@@@U@@@J &z 2 2222222222s'A%A)(A)cKtj}|dtd{V}tdt |t|d{VdS)z?Remove WAF files from all installed sites (global WAF disable).Nz7Global WAF disabled, removing WAF files from %d site(s))rrrsrtr4r|rrrv)rurs rBremove_waf_rules_for_all_sitesrHs  # % %D&&t-@AA A A A A A AE KKA E  'u - ----------rAc6KtjsdStrdat ddSt4d{V dats dddd{VdSt}tj d{V}tj }|D]} |dt|d{Vr&|rt!|d{Vnt#|d{VT#t$$r&}t d||Yd}~d}~wwxYwtsnt d dddd{VdS#1d{VswxYwYdS)zMRedeploy/remove rules.php for users without an explicit waf_enabled override.NTz2waf_default change already in progress, coalescingFz2Failed to apply waf_default change for user %s: %sz1Re-running waf_default change (coalesced request))rrS_apply_waf_default_lockrj_apply_waf_default_pendingr|rrLrQr HostingPanel get_usersrrrsrtrxrrrr) new_default usernamesrurars rBapply_waf_default_changerSs  ,%%''%)" HIII& M M M M M M M M M). &*,,  M M M M M M M M M M M M M M+,,K+8::DDFFFFFFFFI+--D%  !11< ! !"B3H==========7AAAAAAAAA NNL .  KKK L L L? M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M MsOF;AF "D.F/-DF E 'EFE  'F FFdomain timestampcptj|d}|t|d}t|S)a| Generate the disabled-rules.php content for a specific domain. Only includes domain-specific disabled rules. Globally disabled rules are handled separately by filtering them out of rules.php. Args: domain: The domain to generate disabled rules for timestamp: Unix timestamp to embed in the file Returns: PHP file content string F)include_global)tsr)rget_domain_disabledsortedr%)rrdisabled_rule_idsr3s rBgenerate_disabled_rules_phprsN':u)**  D ) . ..rAcJK|j} t||d{V}|dz }t|j|}t |||j|t j|t j |j k | |t d|j dS#t$rA} | |td|j | Yd} ~ dSd} ~ wwxYw)a[ Deploy disabled-rules.php to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd timestamp: Unix timestamp for both file content and DB record updated: Set to add site to if successful failed: Set to add site to if failed Nrqr5disabled_rules_sync_tsz"Updated disabled-rules for site %sz/Failed to update disabled-rules for site %s: %s)r7r$rrr+rrrwhererEexecuterr|rrr) rrrrrBr6rdisabled_rules_pathr8rs rBrrsO"  C 3D)DDDDDDDD&)==1$+yII ) $(     I>>>DD  !T\ 1  '))) D 8$,GGGGG     4 = L           sC C D"!6DD"domainscbKtjstddStdt |rt |}nt }|stddSd}t||ddd| d{VdS) ai Deploy disabled-rules.php to WordPress sites. If domains are specified, only updates sites for those domains. If domains is None, updates all installed sites (e.g., after a global disable/enable). Args: domains: List of domains to update, or None for all sites sink: Optional telemetry sink for remove_site_if_missing zIwordpress security plugin not enabled, skipping disabled-rules deploymentNz5Starting disabled-rules deployment to WordPress sitesz6No WordPress sites found for disabled-rules deploymentcJt||tj||SrG)rr )rrrrBs rBrHz1update_disabled_rules_on_sites..make_tasks%- )TY[['6   rAzdisabled-ruleszdisabled-rules-update-skip-userTrb)rrSr|rrr.r4r_)rrrrHs rBrnrns  ,       KKGHHHNNN&.w77#%%  LMMM    "5     rAc Ktdt}t}tjd5 t t}|s(td ddddStt}|D]"}||j  |#g}| D]\}} tj|} n<#t$r/} t!dt#||| dddd Yd} ~ Od} ~ wwxYw|D]@}t%||d{Vrt'|| ||} | | Ad } t)d t#|| D]&} || | | z}t+j|d did{V'tdt#|t#|nf#t*j$r+tdt#|Yn-t$r!} td| d} ~ wwxYwddddS#1swxYwYdS)z7Update auth.php files for all existing WordPress sites.z4Updating auth.php files for existing WordPress siteszwp-auth-updatez"No installed WordPress sites foundNzSkipping auth update for WordPress sites on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}rMrNrOrrPzwp-plugin-auth-update-skip-userrQrrrXTz8Updated auth.php files for %d WordPress sites, %d failedzLAuth update for WordPress sites was cancelled. Auth was updated for %d sitesz+Error occurred during auth update. error=%s)r|rrr rrrr4rrrrrrrrrrrupdate_site_authrYrrrrr)rrrBrcrrrrrrrrr[r\r]s rBupdate_auth_everywherers KKFGGGeeG UUF    / 0 0@@?  NNN233O"  @AAA@@@@@@@@(--M' 5 5dh'..t4444E+1133 ' ' U # S 1 1II D &)ZZ$'&+%% ("-$E    HHHH""''D3D$????????! +D)WfMMDLL&&&& ' N1c%jj.99 E Ea!n"445neDtDDDDDDDDDD KKJG F      %    KK(G           LLF N N N  }@@@@@@@@@@@@@@@@@@stI;8H AH5D  H E%D>9H>ECHI;7I+?I; I+ I&&I++I;;I?I?cK t||d{V||dS#t$r<}||td||Yd}~dSd}~wwxYw)z/Process authentication setup for a single site.Nz*Failed to update auth for site=%s error=%s)r9rrr|r)rrrrBrs rBrr>s  'i888888888 D     4 8            s+1 A71A22A7c.Ktj|jrdSt |}|dkr&|#t j|d||jd{Vn1t d|tdd|id d d d S)a Checks if the site directory exists. If not, removes the site from the local database and sends a 'site_removed' telemetry event only if deletion is successful. Returns True if the site was removed (directory missing), False otherwise. Parameters: sink: The telemetry/event sink. site: The WPSite object to check and potentially remove. Side effect: If the site is missing and successfully deleted from database, a telemetry event will be sent. FrN site_removedrz@Failed to delete missing site %s from database, no rows affectedz2Failed to delete missing site {site} from databaserrrPzwp-plugin-site-delete-failedrQT) rrisdirrEr-rrrr|rr)rr rows_deleteds rBrrLs w}}T\""ut$$La  &$          N     @!6      4rAfile_permissionscK tj|d{V} t|}nC#t$r6}|jtjtjtjfvrYd}~dSd}~wwxYw tj |j dz}|dkrtj |ddD]} t||5}tj |}|j dz|krtj ||dddn #1swxYwYd#t$rYpt$r<}|jtjkr!td||Yd}~d}~wwxYw tj|n#tj|wxYwdS#t$$r'} td || Yd} ~ dSd} ~ wwxYw) z6Fix data file permissions for a single WordPress site.NFii)r;r>zauth.phprDrqr z"Skipping chmod: %s/%s is a symlinkTz.Failed to fix permissions for site=%s error=%s)rr rrrrrrrstatst_modechmodrfstatrrr|rrrr) rrrr rcurrent_dir_mode file_namefile_fdstrs rBfix_site_data_file_permissionsrus{1)$//////// )(33FF   yU\5; FFFuuuuu    !wv6> 5(('''   &y@@@@GXg..:-1AAAHW.>???@@@@@@@@@@@@@@@)HyEK//@$% ! 0 HV    BHV    t  <     uuuuu sF/F A/*A*#F)A**A//F3;E:/D8D8 DD D D DE: E!E: E!%1EE:EE!!E:%F:FF GGGc\Kt}t}tjd5 t t }|s ddddSddlm}ddlm }|j |j krdnd}|D]\}t||d{Vrt||d{V}|r| |G| |]tdt!|t!|nj#t"j$r+td t!|Yn1t&$r%} td | Yd} ~ nd} ~ wwxYwddddS#1swxYwYdS) z Fix data file permissions for all WordPress sites with imunify-security plugin installed. Args: sink: The telemetry/event sink zwp-plugin-fix-permissionsNr)r)Pleski z=Fixed data file permissions for %d WordPress sites, %d failedzOFixing data file permissions was cancelled. Permissions were fixed for %d sitesz1Error occurred during permission fixing. error=%s)rr rrrr4+defence360agent.subsys.panels.hosting_panelr#defence360agent.subsys.panels.pleskrNAMErrrr|rrrrrrr) rfixedrBrcrrrrsuccessrs rB$fix_data_file_permissions_everywherers EEE UUF    : ; ;00/  NNN233O" 00000000       B A A A A A&, ::  ( % %/d;;;;;;;; >*!!%IIdOOOOJJt$$$$ KKE F     %    KK&E           LLCU         [000000000000000000sNF!D*,B=D*)F!*7F!F!# F,F F! FF!!F%(F%cdeZdZdZdZdZdZdZdddd d d d d ZdZ dZ dZ dZ de ddfdZdZdS)rz Handles installation of imunify-security plugin on WordPress sites. This class processes WordPress sites and installs the imunify-security plugin, including setting up authentication, scan data files, and rules. Tinstalled_by_imunifyzwp-plugin-installationzwp-plugin-install-skip-userz%Installing imunify-security wp pluginz5Installed imunify-security wp plugin on {count} sitesz&Found {count} site(s) for installationz5Failed to install plugin to site={site} error={error}z`Installation of imunify-security wp plugin was cancelled. Plugin was installed for {count} sitesz8Error occurred during plugin installation. error={error}zSkipping installation of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}startcompletefoundr cancelled exception skip_usercH||_||_t|_t|_t|_t|_t|_t|_d|_ t|_ d|_ dSrG) rrr processed authenticatedrules_installedfailed_rules_updatesdisabled_rules_installedfailed_disabled_rules_updatesr failed_auth _current_site)selfrrs rB__init__zWordPressSiteInstaller.__init__ sy   UU"uu$'EE!(+%-0UU*/355,0rAcKtj|d{V}|s3td|t dd|idddd Sd S) a Check if site is ready for processing. Override in subclasses to implement different readiness checks. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for processing, False otherwise. Nz6WordPress site is not accessible using WP CLI. site=%sz:WordPress site is not accessible using WP CLI. site={site}rrrPzwp-plugin-cli-not-accessiblerQFT)rrr|rr)rrrs rB is_site_readyz$WordPressSiteInstaller.is_site_readys(+'A$'G'G!G!G!G!G!G!G%  NNH    L#TN%:     5trAc|j|t|h||dS)u Record a successfully processed site and persist it to the database. Each site is inserted immediately so that it is tracked in the DB at all times — even if the overall installation loop is cancelled mid-run. Override in subclasses to implement different recording logic. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. N)rrr5_stamp_disabled_rules_sync_tsrrrs rB_record_processed_sitez-WordPressSiteInstaller._record_processed_site3sD 4   v&&& **400000rAcK|j}d|_ t|d{Vn3#t$r&}td||Yd}~nd}~wwxYw|jrt j|d{VdSdS)aRevert the site that was mid-processing when cancellation occurred. Deletes data files and, if this processor installs plugins, attempts to uninstall the partially-installed plugin. Each step runs independently so one failure doesn't skip the other. Nz5Failed to delete data files for in-flight site %s: %s)rrrr|rinstall_pluginrtry_plugin_uninstall)rrrs rB_revert_in_flight_sitez-WordPressSiteInstaller._revert_in_flight_siteEs!! %d++ + + + + + + + +    NNG            1*400 0 0 0 0 0 0 0 0 0 1 1s( AAArrENc||jvrdS|jdStj|jtj|jkdS)z Stamp disabled_rules_sync_ts for a single site after it has been inserted into the DB. Called from ``_record_processed_site`` so the DB row already exists. Nr)rrrrrrEr)rrs rBrz4WordPressSiteInstaller._stamp_disabled_rules_sync_tsYse t4 4 4 F  ! ) F#'#9   % %5 6 6wwyyyyyrAc Kt|jdg}tj|j5 t|js{td|j |rLtj |ddid{V}|D]2}t|trtd|3cdddSt|jdt!|jt#}t%d{V}t'j|_t+d{V}t-t.}|jD]"}||j|#|D]B\} } t7j| } | j} nL#t$r?} t=|jd t!| | | d d d |j Yd} ~ gd} ~ wwxYw tA| d{V}n/#t$r"td| dd}YnwxYw|s)td| t!| tC|j"| |d{V\}}}tG| }| D]/}tI|j"|d{Vr |%|d{Vs<||_&tO||| |||}tQ|| d{V}tS||| |d{VtU||| |d{VtW|| |j,|j-d{V|r%|r#t]|| ||j/|j0d{V|r(tc|| |j|j2|j3d{V|j4rtkj6|d{Vtkj7|d{V}|rtqj9||}|:||d|_&|tj;tyj=|j"|j>||#t$rY} d|_&t?|jd|t| Yd} ~ )d} ~ wwxYwDt|jdt!|j |j-r-tdt!|j-|j0r-tdt!|j0|j3r-tdt!|j3n#tjA$ro|j&r|Bd{Vt|jdt!|j YnXt$rL} t?|jdt| d} ~ wwxYw|rLtj |ddid{V}|D]2}t|trtd|3nT#|rLtj |ddid{V}|D]3}t|trtd|3wwxYwdddn #1swxYwY|j S)z Process WordPress sites for imunify-security plugin operations. Returns: set: The set of successfully processed sites. rz'No WordPress sites found, nothing to dorXTNzFailed to send telemetry: %sr)rMrrrrPrQrUrVzFWAF disabled for user %s, skipping WAF rules deployment for %d site(s)rrrr)rrrzFailed to authenticate %d sitesz&Failed to install wp-rules on %d sitesz,Failed to install disabled-rules on %d sitesrr)r)Cr|rmessagesr rrrIrrrrrr isinstancerrformatrr rr rr&rrrrrrrrrlog_fingerprint_skip_userrvrrr)rrrr*r$rrrrrrGrrrrrrrplugin_installrrrrrrrtelemetry_eventrreprrr)rtelemetry_tasksresultsrryrArrrrrrrarrdrrrrrrrs rBrzWordPressSiteInstaller.runhs   DM'*+++   " "4> 2 2W W V z*KK IJJJ>R#$+N(%<@%%G#*%fi88"NN >kW W W W W W W W  M'*11DJ1HH ,~~ &7%8%8888888 *.&!=!?!???????!,D 1 1  J99D!$(+2248888#0"5"5"7"7OOJC!$'L$5$5 #,#4$ ! ! !# M+6),U(+*/)) #,&1(,(F    ! ! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +' ?$JJ 6 9l &&'%:($C$CM %bb!7 4!H!HHHHHHH%$^)-););D)A)A#A#A#A#A#A#A) (15D.): . . ( $ /)1 )))I.H $i..((((((H #8 $ )*3)1 ### #< $ -*3)1 ### #3 $ ) $ 2 $ 0 ## ," "&>$($-$0$($8$($= '"'"!"!"!"!"!"!"!" +"&D$($-$($:$($A$($F '"'"!"!"!"!"!"!"!" $2?&)&8&>&> > > > > > > >-0,B4,H,H&H&H&H&H&H&HG&P'-'@w'O'O!77gFFF15D.+22 ' 3$-$8-1Y.2.B-107 %&%&%&!"!"     )15D."LL $ g 6 = =)-T%[[!>!"!"ybF M*-443t~;N;N4OO#NN9D,--,NN@D5665NNFD>?? )   %855777777777 M+.55!$.116     M+.55DKK5HH   #$+N(%<@%%G#*%fi88"NN > #$+N(%<@%%G#*%fi88"NN > gW W W W W W W W W W W W W W W r~s];5W7A]; C1W7G.-W7. H785H2-W72H77W7;IW7)I=:W7<I==A>W7<R'W7F R'%W7' T 1AT ?W7T C,W76\7A;[ 2\4 [ =A[[  \ A];A],,];;]?]?)__name__ __module__ __qualname____doc__rrrIrrrrrrrrrr@rArBrrsN,O(I =8K9H 5 G 7H( 1 1 18111$111( A& AT A A A AcccccrArcVeZdZdZdZdZdZdZdddd d d d d ZfdZ dZ fdZ xZ S)rz Handles adoption of existing WordPress sites with imunify-security plugin. Adoption is a special case of installation where the site already has the plugin installed but is not tracked in our database. F site_foundzwp-plugin-adoptionzwp-plugin-adopt-skip-userz#Adopting imunify-security wp pluginz3Adopted imunify-security wp plugin on {count} sitesz"Found {count} site(s) for adoptionz3Failed to adopt plugin to site={site} error={error}zZAdoption of imunify-security wp plugin was cancelled. Plugin was adopted for {count} sitesz4Error occurred during plugin adoption. error={error}zSkipping adoption of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}rct||dtjtjD|_dS)Nch|] }|j Sr@)rE)rrs rB z0WordPressSiteAdopter.__init__..os'" " " AI" " " rA)superrrselectrEexisting_docroots)rrr __class__s rBrzWordPressSiteAdopter.__init__lsR u%%%" " ,3M4IJJ" " " rAc|j||j|jvr1t |t ||rt ||nt|h||dS)a Record a successfully adopted site and persist it immediately. For adoption, sites that already exist in the database (flagged as manually deleted) have their flag cleared. New sites are inserted into the database right away. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. N) rrrErr,r7r8r5rrs rBrz+WordPressSiteAdopter._record_processed_sitess 4   <41 1 1 ' - - -  & & & 3#D'222 "D6 * * * **400000rAcKt|d{VsdStj|d{V}|std|dSdS)z Check if site is ready for adoption. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for adoption, False otherwise. NFz2Plugin not installed on site %s, skipping adoptionT)rrrrr|r)rrr"rs rBrz"WordPressSiteAdopter.is_site_readysWW**400000000 5!4T::::::::   NND   5trA) rrrrrrrIrrrrr __classcell__)rs@rBrrNsN"O$I ;6I5F 3L 7H$     111.rAr)rrG)FN)rEN)NN)rrrloggingrrr  collectionsrcollections.abcrrdistutils.versionr functoolsrpathlibrdefence360agent.apir defence360agent.contracts.configr r~r r r rdefence360agent.filesrrdefence360agent.sentryrdefence360agent.utilsrdefence360agent.utils.fd_opsrrrrrdefence360agent.subsys.panelsr"defence360agent.wordpress.wp_rulesrrdefence360agent.model.wordpressrr&defence360agent.model.wp_disabled_rulerdefence360agent.wordpressrr#defence360agent.wordpress.constantsr defence360agent.wordpress.utilsr!r"r#r$r%r&r'r(r)r*r+)defence360agent.wordpress.site_repositoryr,r-r.r/r0r1r2r3r4r5r6r7r8$defence360agent.wordpress.proxy_authr9 getLoggerrr|rCrKLockrrrWr[rHrLrQrTrXstrr]rgrirftupler`rlrprvrxCOMPONENTS_DB_PATHrdictrr struct_passwdrrrrrrrrrrrrintr$r(rrr/r1r9rrr@rGr_rdrgrirkrlrortrvrrrrfloatrrrnrrrrrrrr@rArBrs   ######////////************21111111......****** 766666777777BAAAAAAAAAAAAA44444444CCCCCC                           KJJJJJ  8 $ $&',.." #$.!$$$$$$$$$$$d *t * * * *!c!!!!" - U4t#34     ,# ,%c :J , , , ,CD CD#3#4####TJ < << < < < <"U"td{""""J&&& t (9 t     ;& ;6B ; ; ; ;F80A8888::d::::&777B!!! ! ! ! 3    `;`;`;FF>))#))))X";";";J$ $ $ P26;;#&v;;;;;n  n  nn nnnnbS0#-%#-T#-d#-#-#-#-L(7<>>$*P*P*P*PZ + + + +@ d6l t    (4#4$4444n 3c 3d 3 3 3 3 ....,M,M,M,M^///#////0% %  % %  %  %  % % % % R!% // #Y / ////dGGGGT    &V&&&&&R5 5$'5 5555p:::zaaaaaaaaH RRRRR1RRRRRrAdefence360agent/wordpress/__pycache__/plugin.cpython-311.pyc0000644000000000000000000026057200000000000021022 0ustar r_jI( ddlZddlZddlZddlZddlZddlZddlmZddlm Z m Z ddl m Z ddl mZddlmZddlmZddlmZmZmZmZmZdd lmZmZdd lmZdd lm Z dd l!m"Z"m#Z#m$Z$m%Z%dd lm&Z&ddl'm(Z(ddl)m*Z*m+Z+ddl,m-Z-m.Z.ddl/m0Z0ddl1m2Z2m3Z3ddl4m5Z5ddl6m7Z7m8Z8m9Z9m:Z:m;Z;mZ>m?Z?m@Z@mAZAddlBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZLmMZMmNZNmOZOddlPmQZQejReSZTedZUdZVejWZXdaYdZZdZ[de\fdZ]de\fdZ^de\fdZ_de\fdZ`deafd Zbd!Zcd"Zdd#Zedefe\e\e\ffd$Zgd%eadefe\eaffd&Zhd%eade\fd'Zid%eade\fd(Zjd%eade\fd)Zked*Zld%ead+efd,Zmd-edendzfd.Zod/Zpd0end1ejqdenfd2Zrd1ejqd+efd3Zsd4ejtfd5Zudvd7evfd8Zwd9Zxd:Zyd;Zzdeafd<Z{d=Z|d>e.fd?Z}d>e.de~fd@ZdAZdBZ dwdCee.fdDZdEeve.fdFZdddGd>e.dHeadIend1ejqdzdJedzddf dKZdddGd>e.dLend1ejqdzdJedzfdMZdddGd>e.dNend1ejqdzdJedzddf dOZdEeve.de~fdPZd>e.d1ejqdQeadRedSeddf dTZ dxdEeve.dUe e.ejqeege dfdVeadWeadXe\ddf dYZdwdQeaddfdZZd-ed[e\ddfd\ZejWZdadyd]Zdyd^Zd_eaddfd`ZdEeve.ddfdaZd%eaddfdbZd%eaddfdcZdyddZdydeZdfeadgedeafdhZd>e.d1ejqdgedRedSeddf diZ dzdjeveadzddfdkZdwdlZdmZd>e.de\fdnZd>e.doe~de\fdpZdqZGdrdsZGdtdueZdS){N) defaultdict) AwaitableCallable) LooseVersion)cache)Path) inactivity)MalwareScanScheduleInterval SystemConfigANTIVIRUS_MODEUserTypechoose_value_from_config)IndexWP_RULES) log_message)importer)open_dir_no_symlinks open_nofollow rmtree_fdsafe_dir) Wordpress) hosting_panel)get_wp_rules_dataget_wp_ruleset_version) WordpressSiteWPSite)WPDisabledRule)cli telemetry)PLUGIN_VERSION_FILE) _validate_presetcalculate_next_scan_timestamp$clear_get_cagefs_enabled_users_cacheensure_site_data_directoryformat_php_with_embedded_jsonget_imunify_package_versions get_last_scanget_malware_historyprepare_plugin_configprepare_scan_data!write_plugin_data_file_atomically) clear_manually_deleted_flag delete_siteget_installed_sites_by_domainsget_outdated_sitesget_sites_for_userget_sites_to_adoptget_sites_to_install%get_sites_to_mark_as_manually_deletedget_installed_sitesinsert_installed_sitesmark_site_as_manually_deletedupdate_site_identityupdate_site_version)setup_site_authenticationc0tjdddS)Nz(imav.malwarelib.plugins.schedule_watcherget_user_schedule_config)modulenamedefault)rgetU/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/plugin.py_get_user_schedule_config_imavrCKs$ <9 '   rATFbalancedreturncd ttjS#t$r tcYSwxYwN)boolr WAF_ENABLEDKeyError_LEGACY_WAF_FALLBACKr@rArB_get_global_waf_enabledrLc?$I)*** $$$####$ //cd ttjS#t$r tcYSwxYwrG)rHr WAF_DEFAULTrJrKr@rArB_get_waf_defaultrQjrMrNcX ttjS#t$rYdSwxYw)NF)rHrSECURITY_PLUGIN_ENABLEDrJr@rArB_get_security_plugin_enabledrTqs: I5666 uus  ))cd ttjS#t$r tcYSwxYw)uSRead WORDPRESS.ai_bot_protection from config, defaulting to False. Returns _AI_BOT_PROTECTION_DEFAULT when the config key is missing — e.g. the ai_bot_protection field hasn't rolled out to this install's imunify360 yet, or a sibling package is still on an older schema. Keeps the feature off in all ambiguous cases. )rHrAI_BOT_PROTECTIONrJ_AI_BOT_PROTECTION_DEFAULTr@rArB_get_global_ai_bot_protectionrX|s?*I/000 ***))))*rNcj tj}n#t$r tcYSwxYwt |S)uRead WORDPRESS.ai_bot_protection_preset from config, defaulting to "balanced". Two layers of safety: KeyError on a missing key (older schema, agent upgrade in progress) and _validate_preset() on the value itself (hand-edited override file, future preset rolled in via a sibling package this version doesn't recognise). Both fall back to the same canonical default so all layers — schema, agent, plugin — agree. )rAI_BOT_PROTECTION_PRESETrJ!_AI_BOT_PROTECTION_PRESET_DEFAULTr!)raws rB$_get_global_ai_bot_protection_presetr]sF10 11100001 C  s ##r>overridezglobal kill switchcTtttfS)aRead the three server-wide WAF flags in one call. Returns (security_plugin_enabled, global_waf_enabled, waf_default), each guarded against a missing config key (schema version skew during an agent/imunify-antivirus upgrade) the same way the individual accessors are. )rTrLrQr@rArBwaf_global_snapshotr`s) %&&!! rAusernamects dtfS tdd|\}}n%#t$rt t fcYSwxYw|t jkrt t fSt|tfS)NF WORDPRESS waf_enabledra) rLWAF_SOURCE_KILL_SWITCHrrJrQWAF_SOURCE_DEFAULTr ROOTrHWAF_SOURCE_OVERRIDE)ravaluesources rB#waf_status_and_source_for_user_syncrls " $ $-,,,60    vv 666!!#555556 !!#555 ;;+ ++s/AAc*t|\}}|SrG)rl)raenabled_s rB_is_waf_enabled_for_user_syncrps4X>>JGQ NrAcpKtj}|dt|d{VS)u3Async wrapper — runs config file I/O in executor.N)asyncioget_running_looprun_in_executorrp)raloops rBis_waf_enabled_for_userrvsR  # % %D%% +X      rAcr tdd|\}}n#t$rYdSwxYw|tjkS)NrcrdreF)rrJr rh)rarorks rB$_user_has_explicit_waf_override_syncrxsY,    66 uu X] ""s  &&zD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3 admin_configct}|*tdtjdddfS|||S)z Get user-specific schedule configuration with lazy import fallback. Returns default values if imav.malwarelib is not available. Nz@imav.malwarelib not available, returning default schedule configr)rCloggerdebugIntervalNONE)raryr;s rB_get_user_schedule_configrsU >??' N   }aA%% # #Hl ; ;;rAindexct|}|dStr|D] \}}d|d< tt jr fd|D}|S)uI Retrieve WordPress rules with ANTIVIRUS_MODE handling and global disable filtering. In ANTIVIRUS_MODE, all rules are set to monitoring mode ("pass"). Globally disabled rules are filtered out entirely — they should not appear in rules.php. Domain-specific disables are handled separately via disabled-rules.php. Args: index: The Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data with mode adjusted for ANTIVIRUS_MODE and globally disabled rules removed, or None if rules cannot be loaded. Npassmodec$i|] \}}|v || Sr@r@).0cveparamsglobally_disableds rB z-get_updated_wp_rules_data..s5   V+++ +++rA)rr itemssetrget_global_disabled)r rules_datarrrs @rBget_updated_wp_rules_datars #5))Jt$%++-- $ $KC#F6NNN>@@AA     )//11   rAcHttjdS)z#Clear all WordPress-related caches.N)r#rclear_get_content_dir_cacher@rArB clear_cachesr s#(***#%%%%%rAr user_infoct|}d|D}|D]Dfd|D}|r1t|t}||E|S)Nci|]}|gSr@r@)rpaths rBrzsite_search..s . . .4dB . . .rAc,g|]}||Sr@r@)rritemmatchers rB zsite_search..s*MMM4t9L9LM$MMMrA)key)r0maxlenappend)rrr user_sitesresultmatching_sitesmost_specific_siters ` @rB site_searchrs#I..J . .: . . .F44MMMMM:MMM  4!$^!=!=!=  % & - -d 3 3 3 MrAc:Kt||jd{V}|dd}t|j|\}}}}d} |tjkrt ||||} t|j} t| |d} || | fS)N scan_datecP|ddko|d|S)N resource_typefile) startswith)rrs rBz)_get_scan_data_for_user..>s-40F:* L # #D ) )rA) r'pw_namer?rr~rr"r(r) sinkrry last_scanlast_scan_timeintervalhour day_of_month day_of_weeknext_scan_timemalware_historymalware_by_sites rB_get_scan_data_for_userr"s$D)*;<<<<<<<? ::rA semaphorecK|4d{V |d{Vn4#t$r'}td|Yd}~nd}~wwxYwdddd{VdS#1d{VswxYwYdS)NzTelemetry task failed: ) Exceptionr|error)corores rB_send_telemetry_taskrEs+88888888 8JJJJJJJJ 8 8 8 LL6166 7 7 7 7 7 7 7 7 8888888888888888888888888888888s5AA AAAAA A'*A' coroutinescK|sdStj|fd|D} tj|d{VdS#t$r(}td|Yd}~dSd}~wwxYw)zK Process a list of telemetry coroutines with a concurrency limit.s NcTg|]$}tjt|%Sr@)rr create_taskr)rrrs rBrz+process_telemetry_tasks..Us?     0yAABB   rAzSome telemetry tasks failed: )rr Semaphoregatherrr|r)r concurrencytasksrrs @rBprocess_telemetry_tasksrMs !+..I       E :ne$$$$$$$$$$ ::: 8Q88999999999:sA A3 A..A3cpK ttd}|d{Vt|}n3#t$r&}t d|Yd}~dSd}~wwxYw|st ddSt|}||d}t|S)z Load WordPress rules from the index and format them as PHP. Returns: str or None: PHP-formatted rules data, or None if rules could not be loaded. F)integrity_checkNz>Failed to load wp-rules index: %s, skipping rules installationz>L9;;;;;;;;H(--M& 5 5dh'..t4444,1133k k  U  # S 1 1I(0HH LLE HHHH 2)\ ""#!6h ? ? !UUD3D$????????! R%(%?%E%EEEEEEE%"KK Et%%6**$ +%- %%% *D )**$$$$$$ 4 %&/%- 8 )&/%- "/555555555 D)))),(>t(D(D"D"D"D"D"D"D"/3|,0g>>>$(#:#:7#C#CD,8 ',, ,-= > >,?L ,22 ) 4)-,8)B(?(?-A)-,3 !" !" !"   % I !aUn KK@G     %    KK+G           LL?      */:: : : : : : : : :)/:: : : : : : : : : :eR;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;R;s9Q;>PPQP33QQ QrcKtj|d{V} t|}nn#t$ra}|jtjkrYd}~dS|jtjtjfvr!t d|Yd}~dSd}~wwxYw t|j 5} tj t|d{Vtj|d}n#tj|d}wxYwtj|j|ddddS#1swxYwYdS#t&$r|dkrtj|wxYw)Nz/Skipping rmtree: data directory %s is a symlinkdir_fdr)r get_data_dirrOSErrorerrnoENOENTELOOPENOTDIRr|rrparentrr to_threadrosclosermdirr= BaseException)rrr exc parent_fds rBdelete_plugin_filesrIs%d++++++++H %h//  9 $ $ FFFFF 9em4 4 4 NNA8    FFFFF   ho & & 6) ' 6:::::::::       HX]9 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6  Q;; HV    sh. BB:BBBD:1D-3 C*D-*DD- D:-D11D:4D15D::&E cK tj|d{V}|s+t|tj||d{VdStj|d{V}tj|d{Vt |d{Vt|}|tj |d|||S#t$r'}t d||Yd}~dSd}~wwxYw)a7 Remove the imunify-security plugin from a single site, including all cleanup and telemetry. Returns the number of affected sites (should be 1 if deletion was successful). This function is intended to be protected with asyncio.shield to ensure it completes even if the parent task is cancelled. Nruninstalled_by_imunifyrz"Failed to remove plugin from %s %s)ris_plugin_installedprocess_manually_deleted_plugintimerplugin_uninstallrr-rrrrr|r)rrr is_installedraffectedrs rBremove_from_single_siter$hsz # 4T::::::::  1dikk4       1.t44444444"4((((((((("$'''''''''t$$   .         94GGGqqqqqsAC BC DC<<Dc Ktdg}d}tjd5 t t }|D]m} |tjt|||d{Vz }0#tj $r,td|t|YjwxYwn.#t$r!}t d|d}~wwxYw td|t|d{Vn5#td|t|d{VwxYw ddddS#1swxYwYdS)zHRemove the imunify-security plugin from all sites where it is installed.z#Deleting imunify-security wp pluginrzwp-plugin-removalNz_Deleting imunify-security wp plugin was cancelled. Plugin was deleted from %d sites (out of %d)z)Error occurred during plugin deleting. %sz0Removed imunify-security wp plugin from %s sites)r|rr rrrr4rrshieldr$rrrrr)rrr# to_removerrs rBremove_all_installedr(s5 KK5666OH    2 3 3;; ; NNN+--I!   gn/dOLL''!!!!!!HH-KKH I      LLDe L L L    KKB    */:: : : : : : : : : KKB    */:: : : : : : : : : :9;;;;;;;;;;;;;;;;;;slE2 C#,BC8C C C  CD/ C:C55C::D/>1E2/2E!!E22E69E6cK t||t|d{V|tj|d||jdS#t $r'}td||Yd}~dSd}~wwxYw)a Process the manually deleted plugin for a single site. Args: site: The site to process. now: The current time. sink: The telemetry/event sink. telemetry_coros: The list of telemetry coroutines to add the event to. The process includes: - marking the site as manually deleted in the database - removing plugin data files - sending telemetry for manual removal Nremoved_by_userrz>Failed to process manually deleted plugin for site=%s error=%s) r6rrrrrrr|r)rnowrrrs rBrrs %dC000"$'''''''''   '              L            sAA B %BB freshly_installed_sitescdKg} t|}|r0tj}|D]}t||||d{Vn2#t$r%}td|Yd}~nd}~wwxYw|rt |d{VdSdS#|rt |d{VwwxYw)a> Tidy up sites that have been manually deleted by the user. Args: sink: The telemetry/event sink. freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Nz&Error occurred during site tidy up. %s)r3r rrr|rr)rr,rto_mark_as_manually_removedr+rrs rBtidy_up_manually_deletedr/s>O;&K #' ' # ' )++C3  5#t_ FFF =uEEEEEEEEF  ;)/:: : : : : : : : : : ; ;? ;)/:: : : : : : : : : ;s0AAB A7A2-B2A77BB/rc NK|sdSt}td{V}tt}|D]"}||j|#|D]-\}} tj|}|j }n3#t$r&} t d|| Yd} ~ Nd} ~ wwxYwt|||d{V\} } } t|} |D]}t||d{Vr t!| | ||| |}t#||d{V}t%||||d{Vt'|| ||d{Vx#t$r&} t d|| Yd} ~ d} ~ wwxYw/dS)Nrrrz.Failed to update site data on site=%s error=%s)r r&rrrrrrrrrr|rrr)rr*r$rr)rrryrrrrrrarrrrrrrs rBupdate_data_on_sitesr1s  >>L133333333H %%M--dh&&t,,,,$))++22 U  S))I (HH    LL=    HHHH  *$ <HH H H H H H H    -h77   D+D$77777777  -""#%  "/``. A caller writing several files into one site's directory can resolve ``user_info`` and ``data_dir`` once and pass them in, so the owner lookup and directory-ensure are not repeated per file. Nrgid)rrrr$r%r+pw_gid)rr2r3rr php_contents rB_write_json_php_data_filer9BsL** 3D)DDDDDDDD/55K%8hIKt|d|||d{VdS)N scan_data.phprr9)rrrrs rBrrZsV $     rArc>Kt|d|||d{VdS)z Write plugin_config.php for a single WordPress site. Separate file from scan_data.php so a config toggle doesn't force rewriting the malware list, and so the mu-plugin hot path loads only what it needs per request. plugin_config.phprNr<)rrrrs rBrrjsV $     rAc&K|sdSd}tt}|D]"}||j|#|D]\}} t j|}|j}n3#t$r&}t d||Yd}~Md}~wwxYwt|} |D]S} t|| |d{V|dz }!#t$r&}t d||Yd}~Ld}~wwxYw|S)us Rewrite plugin_config.php on every managed site in one pass. Used by the ConfigUpdate handler that reacts to WORDPRESS.ai_bot_protection toggles. Writes only plugin_config.php — scan_data.php is untouched, so a toggle doesn't churn the (potentially large) malware payload or wait on a scan cycle. No sink is needed: unlike update_data_on_sites we emit no telemetry here — the per-site write loop just needs local file I/O plus the process-level logger for errors. Returns the number of sites successfully updated so the caller can decide whether to advance its cached state. rrN)rr{z6Failed to update plugin_config.php on site=%s error=%s) rrrrrrrrrr|rr)r) rrrrrrrrarrs rBupdate_plugin_config_on_sitesr@s qG .9->->M--dh&&t,,,,(..00Z  S))I (HH    LL=    HHHH  .h77   D /-91     L   Ns0A88 B(B##B(?C D 'DD  wp_rules_phprfailedcK|j} t||d{V}|dz }t|||j|||t d|jdS#t$rA}||t d|j|Yd}~dSd}~wwxYw)a= Deploy wp-rules to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd wp_rules_php: Formatted PHP rules content updated: Set to add site to if successful failed: Set to add site to if failed N rules.phpr5zUpdated wp-rules for site %sz)Failed to update wp-rules for site %s: %s) r7r$r+rrr|rdocrootrr) rrrArrBr6r rules_pathrs rBupdate_wp_rules_for_siterGs"  C 3D)DDDDDDDD + )  $(      D 2DLAAAAA     4 7 L           sA(A55 C?6B;;C make_task task_name fingerprintskip_waf_disabledc Kt}t}tj|5 t j}t t } |D]"} | | j| #g} | D]+\} } tj | }|j }nW#t$rJ}td|t| | |ddd|| D]} || Yd}~rd}~wwxYw|rr t#|d{V}n/#t$r"t$d|d d}YnwxYw|s*t$d |t| | D]:} t+|| d{Vr| || |||;-d }t-d t| |D]&}| |||z}t/j|d did{V't j|z }t$d|t|t||nh#t.j$r,t$d|t|Yn.t$r"}t$d||d}~wwxYwddddS#1swxYwYdS)a6 Run a per-site async deployment over a list of WordPress sites. Groups sites by user, resolves UIDs, then runs tasks concurrently in batches. Args: sites: WordPress sites to deploy to make_task: Callable that creates a coroutine for one site. Signature: (site, user_info, updated_set, failed_set) -> awaitable task_name: Human-readable name for logging and inactivity tracking fingerprint: Sentry fingerprint for user-lookup failures sink: Optional telemetry sink for remove_site_if_missing zwSkipping {task} update for {count} site(s) belonging to user {user} because username retrieval failed. Reason: {reason})rcountuserreasonr wordpress format_argslevel componentrJNBCould not check WAF status for user %s, proceeding with deploymentTexc_infoz-WAF disabled for user %s, skipping %d site(s)rrreturn_exceptionsz@%s deployment complete. Updated: %d, Failed: %d, Duration: %.2fsz-%s deployment was cancelled. Updated %d sitesz-Error occurred during %s deployment. error=%s)rr rrr rrrrrrrrrrrrrvr|rrrrangerrrrr)rrHrIrJrKrrrB start_timerrrrrrrarrdmax_concurrentibatchelapseds rB_deploy_to_sitesr_s0eeG UUF    y ) )SSR J'--M 5 5dh'..t4444E#0#6#6#8#8- N- NZ # S 1 1I(0HH >%.%(__$'&+ %% ("-$/    !+)) 4((((HHHH#&%! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +'! K$ OO !&NND3D$????????! LL4GV!L!LMMMMN  N1c%jj.99 E Ea!n"445neDtDDDDDDDDDDikkJ.G KK#G F      %    KK?G           LL?      [SSSSSSSSSSSSSSSSSSsKA(I%)CI% DADI%DI%D54I%5)E!I% E!!DI%$K%8K K K (KK  KK!KcKtt}|stddSfd}t ||ddd|d{VdS)zHDeploy pre-formatted wp-rules PHP content to all active WordPress sites.zNo active WordPress sites foundNc*t||||SrG)rG)rrrrBrAs rBrHz'_deploy_wp_rules_php..make_taskYs ' )\7F   rAzwp-ruleszwp-rules-update-skip-userTrIrJrKr)rr4r|r}r_)rArinstalled_sitesrHs` rB_deploy_wp_rules_phprdPsNNN)++O  6777     /     rA is_updatedcKtjstddS|stddStdt |}|stddSt |}||d}t|}t|d{VdS)z Hook that runs when wp-rules files are updated. Extracts wp-rules.yaml from wp-rules.zip and deploys to all active WordPress sites. Args: index: Index object for wp-rules is_updated: Whether files were actually updated zCwordpress security plugin not enabled, skipping wp-rules deploymentNz)wp-rules not updated, skipping deploymentz/Starting wp-rules deployment to WordPress sitesz,No valid wp-rules found, skipping deploymentr) rrSr|rrrrr%rd)rrerrrrAs rBupdate_wp_rules_on_sitesrghs  ,        ?@@@ KKABBB-e44M  CDDD.e44#L1>>L | , ,,,,,,,,,,rAc`KtjstddStrdatddSt4d{V datdtd{V}|s.td dddd{VdSt|d{Vt sntd dddd{VdS#1d{VswxYwYdS) aN Re-deploy rules.php to all WordPress sites. Used when globally disabled rules change, requiring rules.php to be regenerated with updated rule filtering. Uses a coalescing lock: if a redeployment is already running, the request is merged into the current run rather than starting a duplicate deployment. zEwordpress security plugin not enabled, skipping wp-rules redeploymentNTz5wp-rules redeployment already in progress, coalescingFz6Starting wp-rules redeployment (global disable change)z(Could not load wp-rules for redeploymentz4Re-running wp-rules redeployment (coalesced request)) rrSr|r_redeploy_rules_php_locklocked_redeploy_rules_php_pendingrrrd)rAs rBredeploy_rules_phprlsS  ,      &&((&*# KLLL'PPPPPPPP P*/ ' KKH   "3!4!4444444L IJJJPPPPPPPPPPPPPP'|44 4 4 4 4 4 4 4.  KKN O O O! P PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPs/AD8D D'*D'cZKtd{Vtd{VdS)a9Global WAF turn-on: deploy rules.php and disabled-rules.php (stamping disabled_rules_sync_ts) to all sites, matching install-with-WAF-on. Wraps rather than extends redeploy_rules_php, which is also the global-rule-change path where restamping sync_ts would skip unconsumed changelog actions. N)rlupdate_disabled_rules_on_sitesr@rArBredeploy_waf_for_all_sitesrosJ    ( * **********rArEct t|}n#t$rYdSt$rg}|jtjtjfvr!t d|Yd}~dSt d||Yd}~dSd}~wwxYw dD]t} tj ||t d||6#t$rYBt$r'}t d|||Yd}~md}~wwxYw tj |dS#tj |wxYw)z9Remove WAF files from data_dir via a symlink-safe dir fd.Nz3Skipping WAF file removal: data dir %s is a symlinkz"Failed to open data dir for %s: %s)rDdisabled-rules.phpr z!Removed %s from %s (WAF disabled)zFailed to remove %s from %s: %s) rFileNotFoundErrorrrrrr|rrrremoverr)rrEr rr2rs rB_remove_waf_files_for_dirrts %h//   9em4 4 4 NNE    FFFFF 97CHHH;  H  (62222 77%       5x!   sa B B:B (B  BD!2C  D! DD! D D=D!DD!!D7cK|D]|} tj|d{V}n8#t$r+}td|j|Yd}~Nd}~wwxYwt jt||jd{V}dS)aRemove WAF files (rules.php, disabled-rules.php) from the given sites. Deletion goes through open_dir_no_symlinks + dir_fd so a site owner cannot redirect the root agent's removal via a symlinked data dir, the same symlink-safe pattern as delete_plugin_files. Nz%Failed to resolve data dir for %s: %s) rr rr|rrErrrrt)rrrrs rB_remove_waf_files_from_sitesrvs     -d33333333HH    LL7q    HHHH    %x             s# A!AAc t KtjsdStj} |dt j|d{V n,#t$rt d|YdSwxYw|dtd{V} fd|D}|sdSt}t}td{V}|r|D]}t| |||d{Vnt dtj}t} t} |D]}t| || | d{Vtd|t#|t#|t#| t#| dS)u\Deploy rules.php and disabled-rules.php for a user's sites (WAF turn-on). Caller must have confirmed WAF is enabled for the user. disabled-rules.php is deployed even if the ruleset fails to load, because it stamps disabled_rules_sync_ts — matching install, which writes it whenever WAF is on regardless of rules.php content. Nz.User %s not found, skipping WAF rules redeployc4g|]}|jjk|Sr@rpw_uidrsrs rBrz)redeploy_waf_for_user..(@@@aey/?&?&?!&?&?&?rAz)Could not load wp-rules for user redeployz[Redeployed WAF artifacts for user %s: rules %d ok/%d failed, disabled-rules %d ok/%d failed)rrSrrrsrtrgetpwnamrJr|rr4rrrGr update_disabled_rules_for_siterr) rarurrrrBrArdisabled_rules_ts dr_updated dr_failedrs @rBredeploy_waf_for_userrsE  ,  # % %D..tS\8LLLLLLLL  .Dr}rA) rrrsrtrr~rJr|rr4rv)rarurrrs @rBremove_waf_rules_for_userr8s  # % %D..tS\8LLLLLLLL  ;X      &&t-@AA A A A A A AE@@@@U@@@J &z 2 2222222222s'A%A)(A)cKtj}|dtd{V}tdt |t|d{VdS)z?Remove WAF files from all installed sites (global WAF disable).Nz7Global WAF disabled, removing WAF files from %d site(s))rrrsrtr4r|rrrv)rurs rBremove_waf_rules_for_all_sitesrHs  # % %D&&t-@AA A A A A A AE KKA E  'u - ----------rAc6KtjsdStrdat ddSt4d{V dats dddd{VdSt}tj d{V}tj }|D]} |dt|d{Vr&|rt!|d{Vnt#|d{VT#t$$r&}t d||Yd}~d}~wwxYwtsnt d dddd{VdS#1d{VswxYwYdS)zMRedeploy/remove rules.php for users without an explicit waf_enabled override.NTz2waf_default change already in progress, coalescingFz2Failed to apply waf_default change for user %s: %sz1Re-running waf_default change (coalesced request))rrS_apply_waf_default_lockrj_apply_waf_default_pendingr|rrLrQr HostingPanel get_usersrrrsrtrxrrrr) new_default usernamesrurars rBapply_waf_default_changerSs  ,%%''%)" HIII& M M M M M M M M M). &*,,  M M M M M M M M M M M M M M+,,K+8::DDFFFFFFFFI+--D%  !11< ! !"B3H==========7AAAAAAAAA NNL .  KKK L L L? M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M M MsOF;AF "D.F/-DF E 'EFE  'F FFdomain timestampcptj|d}|t|d}t|S)a| Generate the disabled-rules.php content for a specific domain. Only includes domain-specific disabled rules. Globally disabled rules are handled separately by filtering them out of rules.php. Args: domain: The domain to generate disabled rules for timestamp: Unix timestamp to embed in the file Returns: PHP file content string F)include_global)tsr)rget_domain_disabledsortedr%)rrdisabled_rule_idsr3s rBgenerate_disabled_rules_phprsN':u)**  D ) . ..rAcJK|j} t||d{V}|dz }t|j|}t |||j|t j|t j |j k | |t d|j dS#t$rA} | |td|j | Yd} ~ dSd} ~ wwxYw)a[ Deploy disabled-rules.php to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd timestamp: Unix timestamp for both file content and DB record updated: Set to add site to if successful failed: Set to add site to if failed Nrqr5disabled_rules_sync_tsz"Updated disabled-rules for site %sz/Failed to update disabled-rules for site %s: %s)r7r$rrr+rrrwhererEexecuterr|rrr) rrrrrBr6rdisabled_rules_pathr8rs rBrrsO"  C 3D)DDDDDDDD&)==1$+yII ) $(     I>>>DD  !T\ 1  '))) D 8$,GGGGG     4 = L           sC C D"!6DD"domainscbKtjstddStdt |rt |}nt }|stddSd}t||ddd| d{VdS) ai Deploy disabled-rules.php to WordPress sites. If domains are specified, only updates sites for those domains. If domains is None, updates all installed sites (e.g., after a global disable/enable). Args: domains: List of domains to update, or None for all sites sink: Optional telemetry sink for remove_site_if_missing zIwordpress security plugin not enabled, skipping disabled-rules deploymentNz5Starting disabled-rules deployment to WordPress sitesz6No WordPress sites found for disabled-rules deploymentcJt||tj||SrG)rr )rrrrBs rBrHz1update_disabled_rules_on_sites..make_tasks%- )TY[['6   rAzdisabled-ruleszdisabled-rules-update-skip-userTrb)rrSr|rrr.r4r_)rrrrHs rBrnrns  ,       KKGHHHNNN&.w77#%%  LMMM    "5     rAc Ktdt}t}tjd5 t t}|s(td ddddStt}|D]"}||j  |#g}| D]\}} tj|} n<#t$r/} t!dt#||| dddd Yd} ~ Od} ~ wwxYw|D]@}t%||d{Vrt'|| ||} | | Ad } t)d t#|| D]&} || | | z}t+j|d did{V'tdt#|t#|nf#t*j$r+tdt#|Yn-t$r!} td| d} ~ wwxYwddddS#1swxYwYdS)z7Update auth.php files for all existing WordPress sites.z4Updating auth.php files for existing WordPress siteszwp-auth-updatez"No installed WordPress sites foundNzSkipping auth update for WordPress sites on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}rMrNrOrrPzwp-plugin-auth-update-skip-userrQrrrXTz8Updated auth.php files for %d WordPress sites, %d failedzLAuth update for WordPress sites was cancelled. Auth was updated for %d sitesz+Error occurred during auth update. error=%s)r|rrr rrrr4rrrrrrrrrrrupdate_site_authrYrrrrr)rrrBrcrrrrrrrrr[r\r]s rBupdate_auth_everywherers KKFGGGeeG UUF    / 0 0@@?  NNN233O"  @AAA@@@@@@@@(--M' 5 5dh'..t4444E+1133 ' ' U # S 1 1II D &)ZZ$'&+%% ("-$E    HHHH""''D3D$????????! +D)WfMMDLL&&&& ' N1c%jj.99 E Ea!n"445neDtDDDDDDDDDD KKJG F      %    KK(G           LLF N N N  }@@@@@@@@@@@@@@@@@@stI;8H AH5D  H E%D>9H>ECHI;7I+?I; I+ I&&I++I;;I?I?cK t||d{V||dS#t$r<}||td||Yd}~dSd}~wwxYw)z/Process authentication setup for a single site.Nz*Failed to update auth for site=%s error=%s)r9rrr|r)rrrrBrs rBrr>s  'i888888888 D     4 8            s+1 A71A22A7c.Ktj|jrdSt |}|dkr&|#t j|d||jd{Vn1t d|tdd|id d d d S)a Checks if the site directory exists. If not, removes the site from the local database and sends a 'site_removed' telemetry event only if deletion is successful. Returns True if the site was removed (directory missing), False otherwise. Parameters: sink: The telemetry/event sink. site: The WPSite object to check and potentially remove. Side effect: If the site is missing and successfully deleted from database, a telemetry event will be sent. FrN site_removedrz@Failed to delete missing site %s from database, no rows affectedz2Failed to delete missing site {site} from databaserrrPzwp-plugin-site-delete-failedrQT) rrisdirrEr-rrrr|rr)rr rows_deleteds rBrrLs w}}T\""ut$$La  &$          N     @!6      4rAfile_permissionscK tj|d{V} t|}nC#t$r6}|jtjtjtjfvrYd}~dSd}~wwxYw tj |j dz}|dkrtj |ddD]} t||5}tj |}|j dz|krtj ||dddn #1swxYwYd#t$rYpt$r<}|jtjkr!td||Yd}~d}~wwxYw tj|n#tj|wxYwdS#t$$r'} td || Yd} ~ dSd} ~ wwxYw) z6Fix data file permissions for a single WordPress site.NFii)r;r>zauth.phprDrqr z"Skipping chmod: %s/%s is a symlinkTz.Failed to fix permissions for site=%s error=%s)rr rrrrrrrstatst_modechmodrfstatrrr|rrrr) rrrr rcurrent_dir_mode file_namefile_fdstrs rBfix_site_data_file_permissionsrus{1)$//////// )(33FF   yU\5; FFFuuuuu    !wv6> 5(('''   &y@@@@GXg..:-1AAAHW.>???@@@@@@@@@@@@@@@)HyEK//@$% ! 0 HV    BHV    t  <     uuuuu sF/F A/*A*#F)A**A//F3;E:/D8D8 DD D D DE: E!E: E!%1EE:EE!!E:%F:FF GGGc\Kt}t}tjd5 t t }|s ddddSddlm}ddlm }|j |j krdnd}|D]\}t||d{Vrt||d{V}|r| |G| |]tdt!|t!|nj#t"j$r+td t!|Yn1t&$r%} td | Yd} ~ nd} ~ wwxYwddddS#1swxYwYdS) z Fix data file permissions for all WordPress sites with imunify-security plugin installed. Args: sink: The telemetry/event sink zwp-plugin-fix-permissionsNr)r)Pleski z=Fixed data file permissions for %d WordPress sites, %d failedzOFixing data file permissions was cancelled. Permissions were fixed for %d sitesz1Error occurred during permission fixing. error=%s)rr rrrr4+defence360agent.subsys.panels.hosting_panelr#defence360agent.subsys.panels.pleskrNAMErrrr|rrrrrrr) rfixedrBrcrrrrsuccessrs rB$fix_data_file_permissions_everywherers EEE UUF    : ; ;00/  NNN233O" 00000000       B A A A A A&, ::  ( % %/d;;;;;;;; >*!!%IIdOOOOJJt$$$$ KKE F     %    KK&E           LLCU         [000000000000000000sNF!D*,B=D*)F!*7F!F!# F,F F! FF!!F%(F%cdeZdZdZdZdZdZdZdddd d d d d ZdZ dZ dZ dZ de ddfdZdZdS)rz Handles installation of imunify-security plugin on WordPress sites. This class processes WordPress sites and installs the imunify-security plugin, including setting up authentication, scan data files, and rules. Tinstalled_by_imunifyzwp-plugin-installationzwp-plugin-install-skip-userz%Installing imunify-security wp pluginz5Installed imunify-security wp plugin on {count} sitesz&Found {count} site(s) for installationz5Failed to install plugin to site={site} error={error}z`Installation of imunify-security wp plugin was cancelled. Plugin was installed for {count} sitesz8Error occurred during plugin installation. error={error}zSkipping installation of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}startcompletefoundr cancelled exception skip_usercH||_||_t|_t|_t|_t|_t|_t|_d|_ t|_ d|_ dSrG) rrr processed authenticatedrules_installedfailed_rules_updatesdisabled_rules_installedfailed_disabled_rules_updatesr failed_auth _current_site)selfrrs rB__init__zWordPressSiteInstaller.__init__ sy   UU"uu$'EE!(+%-0UU*/355,0rAcKtj|d{V}|s3td|t dd|idddd Sd S) a Check if site is ready for processing. Override in subclasses to implement different readiness checks. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for processing, False otherwise. Nz6WordPress site is not accessible using WP CLI. site=%sz:WordPress site is not accessible using WP CLI. site={site}rrrPzwp-plugin-cli-not-accessiblerQFT)rrr|rr)rrrs rB is_site_readyz$WordPressSiteInstaller.is_site_readys(+'A$'G'G!G!G!G!G!G!G%  NNH    L#TN%:     5trAc|j|t|h||dS)u Record a successfully processed site and persist it to the database. Each site is inserted immediately so that it is tracked in the DB at all times — even if the overall installation loop is cancelled mid-run. Override in subclasses to implement different recording logic. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. N)rrr5_stamp_disabled_rules_sync_tsrrrs rB_record_processed_sitez-WordPressSiteInstaller._record_processed_site3sD 4   v&&& **400000rAcK|j}d|_ t|d{Vn3#t$r&}td||Yd}~nd}~wwxYw|jrt j|d{VdSdS)aRevert the site that was mid-processing when cancellation occurred. Deletes data files and, if this processor installs plugins, attempts to uninstall the partially-installed plugin. Each step runs independently so one failure doesn't skip the other. Nz5Failed to delete data files for in-flight site %s: %s)rrrr|rinstall_pluginrtry_plugin_uninstall)rrrs rB_revert_in_flight_sitez-WordPressSiteInstaller._revert_in_flight_siteEs!! %d++ + + + + + + + +    NNG            1*400 0 0 0 0 0 0 0 0 0 1 1s( AAArrENc||jvrdS|jdStj|jtj|jkdS)z Stamp disabled_rules_sync_ts for a single site after it has been inserted into the DB. Called from ``_record_processed_site`` so the DB row already exists. Nr)rrrrrrEr)rrs rBrz4WordPressSiteInstaller._stamp_disabled_rules_sync_tsYse t4 4 4 F  ! ) F#'#9   % %5 6 6wwyyyyyrAc Kt|jdg}tj|j5 t|js{td|j |rLtj |ddid{V}|D]2}t|trtd|3cdddSt|jdt!|jt#}t%d{V}t'j|_t+d{V}t-t.}|jD]"}||j|#|D]B\} } t7j| } | j} nL#t$r?} t=|jd t!| | | d d d |j Yd} ~ gd} ~ wwxYw tA| d{V}n/#t$r"td| dd}YnwxYw|s)td| t!| tC|j"| |d{V\}}}tG| }| D]/}tI|j"|d{Vr |%|d{Vs<||_&tO||| |||}tQ|| d{V}tS||| |d{VtU||| |d{VtW|| |j,|j-d{V|r%|r#t]|| ||j/|j0d{V|r(tc|| |j|j2|j3d{V|j4rtkj6|d{Vtkj7|d{V}|rtqj9||}|:||d|_&|tj;tyj=|j"|j>||#t$rY} d|_&t?|jd|t| Yd} ~ )d} ~ wwxYwDt|jdt!|j |j-r-tdt!|j-|j0r-tdt!|j0|j3r-tdt!|j3n#tjA$ro|j&r|Bd{Vt|jdt!|j YnXt$rL} t?|jdt| d} ~ wwxYw|rLtj |ddid{V}|D]2}t|trtd|3nT#|rLtj |ddid{V}|D]3}t|trtd|3wwxYwdddn #1swxYwY|j S)z Process WordPress sites for imunify-security plugin operations. Returns: set: The set of successfully processed sites. rz'No WordPress sites found, nothing to dorXTNzFailed to send telemetry: %sr)rMrrrrPrQrUrVzFWAF disabled for user %s, skipping WAF rules deployment for %d site(s)rrrr)rrrzFailed to authenticate %d sitesz&Failed to install wp-rules on %d sitesz,Failed to install disabled-rules on %d sitesrr)r)Cr|rmessagesr rrrIrrrrrr isinstancerrformatrr rr rr&rrrrrrrrrlog_fingerprint_skip_userrvrrr)rrrr*r$rrrrrrGrrrrrrrplugin_installrrrrrrrtelemetry_eventrreprrr)rtelemetry_tasksresultsrryrArrrrrrrarrdrrrrrrrs rBrzWordPressSiteInstaller.runhs   DM'*+++   " "4> 2 2W W V z*KK IJJJ>R#$+N(%<@%%G#*%fi88"NN >kW W W W W W W W  M'*11DJ1HH ,~~ &7%8%8888888 *.&!=!?!???????!,D 1 1  J99D!$(+2248888#0"5"5"7"7OOJC!$'L$5$5 #,#4$ ! ! !# M+6),U(+*/)) #,&1(,(F    ! ! +,CH,M,M&M&M&M&M&M&M $+++:$%) ' '+ +' ?$JJ 6 9l &&'%:($C$CM %bb!7 4!H!HHHHHHH%$^)-););D)A)A#A#A#A#A#A#A) (15D.): . . ( $ /)1 )))I.H $i..((((((H #8 $ )*3)1 ### #< $ -*3)1 ### #3 $ ) $ 2 $ 0 ## ," "&>$($-$0$($8$($= '"'"!"!"!"!"!"!"!" +"&D$($-$($:$($A$($F '"'"!"!"!"!"!"!"!" $2?&)&8&>&> > > > > > > >-0,B4,H,H&H&H&H&H&H&HG&P'-'@w'O'O!77gFFF15D.+22 ' 3$-$8-1Y.2.B-107 %&%&%&!"!"     )15D."LL $ g 6 = =)-T%[[!>!"!"ybF M*-443t~;N;N4OO#NN9D,--,NN@D5665NNFD>?? )   %855777777777 M+.55!$.116     M+.55DKK5HH   #$+N(%<@%%G#*%fi88"NN > #$+N(%<@%%G#*%fi88"NN > gW W W W W W W W W W W W W W W r~s];5W7A]; C1W7G.-W7. H785H2-W72H77W7;IW7)I=:W7<I==A>W7<R'W7F R'%W7' T 1AT ?W7T C,W76\7A;[ 2\4 [ =A[[  \ A];A],,];;]?]?)__name__ __module__ __qualname____doc__rrrIrrrrrrrrrr@rArBrrsN,O(I =8K9H 5 G 7H( 1 1 18111$111( A& AT A A A AcccccrArcVeZdZdZdZdZdZdZdddd d d d d ZfdZ dZ fdZ xZ S)rz Handles adoption of existing WordPress sites with imunify-security plugin. Adoption is a special case of installation where the site already has the plugin installed but is not tracked in our database. F site_foundzwp-plugin-adoptionzwp-plugin-adopt-skip-userz#Adopting imunify-security wp pluginz3Adopted imunify-security wp plugin on {count} sitesz"Found {count} site(s) for adoptionz3Failed to adopt plugin to site={site} error={error}zZAdoption of imunify-security wp plugin was cancelled. Plugin was adopted for {count} sitesz4Error occurred during plugin adoption. error={error}zSkipping adoption of WordPress plugin on {count} site(s) because they belong to user {user} and it is not possible to retrieve username for this user. Reason: {reason}rct||dtjtjD|_dS)Nch|] }|j Sr@)rE)rrs rB z0WordPressSiteAdopter.__init__..os'" " " AI" " " rA)superrrselectrEexisting_docroots)rrr __class__s rBrzWordPressSiteAdopter.__init__lsR u%%%" " ,3M4IJJ" " " rAc|j||j|jvr1t |t ||rt ||nt|h||dS)a Record a successfully adopted site and persist it immediately. For adoption, sites that already exist in the database (flagged as manually deleted) have their flag cleared. New sites are inserted into the database right away. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. N) rrrErr,r7r8r5rrs rBrz+WordPressSiteAdopter._record_processed_sitess 4   <41 1 1 ' - - -  & & & 3#D'222 "D6 * * * **400000rAcKt|d{VsdStj|d{V}|std|dSdS)z Check if site is ready for adoption. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for adoption, False otherwise. NFz2Plugin not installed on site %s, skipping adoptionT)rrrrr|r)rrr"rs rBrz"WordPressSiteAdopter.is_site_readysWW**400000000 5!4T::::::::   NND   5trA) rrrrrrrIrrrrr __classcell__)rs@rBrrNsN"O$I ;6I5F 3L 7H$     111.rAr)rrG)FN)rEN)NN)rrrloggingrrr  collectionsrcollections.abcrrdistutils.versionr functoolsrpathlibrdefence360agent.apir defence360agent.contracts.configr r~r r r rdefence360agent.filesrrdefence360agent.sentryrdefence360agent.utilsrdefence360agent.utils.fd_opsrrrrrdefence360agent.subsys.panelsr"defence360agent.wordpress.wp_rulesrrdefence360agent.model.wordpressrr&defence360agent.model.wp_disabled_rulerdefence360agent.wordpressrr#defence360agent.wordpress.constantsr defence360agent.wordpress.utilsr!r"r#r$r%r&r'r(r)r*r+)defence360agent.wordpress.site_repositoryr,r-r.r/r0r1r2r3r4r5r6r7r8$defence360agent.wordpress.proxy_authr9 getLoggerrr|rCrKLockrrrWr[rHrLrQrTrXstrr]rgrirftupler`rlrprvrxCOMPONENTS_DB_PATHrdictrr struct_passwdrrrrrrrrrrrrintr$r(rrr/r1r9rrr@rGr_rdrgrirkrlrortrvrrrrfloatrrrnrrrrrrrr@rArBrs   ######////////************21111111......****** 766666777777BAAAAAAAAAAAAA44444444CCCCCC                           KJJJJJ  8 $ $&',.." #$.!$$$$$$$$$$$d *t * * * *!c!!!!" - U4t#34     ,# ,%c :J , , , ,CD CD#3#4####TJ < << < < < <"U"td{""""J&&& t (9 t     ;& ;6B ; ; ; ;F80A8888::d::::&777B!!! ! ! ! 3    `;`;`;FF>))#))))X";";";J$ $ $ P26;;#&v;;;;;n  n  nn nnnnbS0#-%#-T#-d#-#-#-#-L(7<>>$*P*P*P*PZ + + + +@ d6l t    (4#4$4444n 3c 3d 3 3 3 3 ....,M,M,M,M^///#////0% %  % %  %  %  % % % % R!% // #Y / ////dGGGGT    &V&&&&&R5 5$'5 5555p:::zaaaaaaaaH RRRRR1RRRRRrAdefence360agent/wordpress/__pycache__/proxy_auth.cpython-311.opt-1.pyc0000644000000000000000000001662300000000000022661 0ustar r_jHNddlZddlZddlZddlZddlZddlmZmZddlmZddl m Z ddl m Z ddl mZmZmZejeZedZd Zd Zd Zed ZdZdZeddefdZdededefdZdededdfdZ dej!ddfdZ"dS)N)datetime timedelta) lru_cache)Path)atomic_rewrite)ensure_site_data_directoryformat_php_with_embedded_json!write_plugin_data_file_atomicallyH)hoursz#/etc/imunify-agent-proxy/jwt-secretz'/etc/imunify-agent-proxy/jwt-secret.oldzimunify-agent-proxy)daysc tjt}|j}n#t$rd}YnwxYwt j|z t kS)Ng) osstatJWT_SECRET_PATHst_mtimeFileNotFoundErrorrnow timestampSECRET_EXPIRATION_TTL total_seconds)rrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/proxy_auth.pyis_secret_expiredrs|!w''=     ""X-  - - / / 0s # 22cKtt} tdt jd}|jddd|dt||dttd t d S#t$r(}td |d Yd }~d Sd }~wwxYw) zRotate the proxy JWT secret on disk: backup current to .old and write a fresh 32-byte secret atomically. Invalidates the in-process cache so subsequent generate_token() calls read the new secret. zRotating proxy auth secret iT)modeparentsexist_oki)r)uidbackup permissionsz'Got error while rotating the secret: %s)exc_infoN)rrloggerinfosecrets token_bytesparentmkdirtouchrstrJWT_SECRET_PATH_OLDload_secret_from_file cache_clear Exceptionerror) secret_path stub_secretes r rotate_secretr5*s ''K  0111)"--   eTD IIIu%%%  *++      ))+++++     5q4           sB B:: C,C''C,returnc^ ttd5}|cdddS#1swxYwYdS#t$r"t dtt$r!}t d|d}~wwxYw)z.Load JWT secret from the configured file path.rbNzJWT secret file not found at %szFailed to read JWT secret: %s)openrreadstriprr%r1r0)fr4s rr.r.Cs /4 ( ( $A6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $  6HHH  4a888 s9A&A  A AAAA4B, B''B,usernamedocrootctjtz}|||d} ddl}||t d}|S#t $r!}td|d}~wwxYw)z Generate a JWT token for the given username and docroots. Args: username: The username for the token docroot: document root paths the user has access to Returns: The JWT token string )expr> site_pathrNHS256) algorithmz Failed to generate JWT token: %s) rutcnowDEFAULT_TOKEN_EXPIRATIONjwtencoder.r0r%r1)r>r?exp_timeclaimsrGtokenr4s rgenerate_tokenrLQs  #;;H8' J JF    6#8#:#:g NN  7;;; s)A A8A33A8rKgidclK tj|}t||d{V}|dz }d|i}t|}t jt ||||d{Vtd||dS#t$r"} t d|| d} ~ wwxYw)z Create the auth.php file in the site's imunify-security directory. Args: site: WPSite instance token: JWT token string uid, gid: int used for file creation Nzauth.phprKz'Created auth.php file for site %s at %sz.Failed to create auth.php file for site %s: %s) pwdgetpwuidrr asyncio to_threadr r%r&r0r1) siterKr!rM user_infodata_dirauth_file_path auth_data php_contentr4s rcreate_auth_php_filerYmsL%% 4D)DDDDDDDD!J.e$ 3I>>  -                5t^       EtQOOO sBB B3B..B3rTc2K t|jt|j}t |||j|jd{Vtd|dS#t$r"}t d||d}~wwxYw)z Set up authentication for a site by creating JWT token and auth.php file. Args: site: WPSite instance user_info: pwd.struct_passwd data Nz.Successfully set up authentication for site %sz/Failed to set up authentication for site %s: %s) rLpw_namer,r?rYpw_uidpw_gidr%r&r0r1)rSrTrKr4s rsetup_site_authenticationr^s y0#dl2C2CDD" %)9+;           DdKKKKK  =tQ     sA$A** B4BB)#rQloggingrrOr'rr functoolsrpathlibrdefence360agent.utilsrdefence360agent.wordpress.utilsrr r getLogger__name__r%rFrr-PROXY_SERVICE_NAMErrr5bytesr.r,rLintrY struct_passwdr^rrls ((((((((000000  8 $ $$92...7?*! q)))       2 1 u    S338%C%3%4%%%%P& rkdefence360agent/wordpress/__pycache__/proxy_auth.cpython-311.pyc0000644000000000000000000001662300000000000021722 0ustar r_jHNddlZddlZddlZddlZddlZddlmZmZddlmZddl m Z ddl m Z ddl mZmZmZejeZedZd Zd Zd Zed ZdZdZeddefdZdededefdZdededdfdZ dej!ddfdZ"dS)N)datetime timedelta) lru_cache)Path)atomic_rewrite)ensure_site_data_directoryformat_php_with_embedded_json!write_plugin_data_file_atomicallyH)hoursz#/etc/imunify-agent-proxy/jwt-secretz'/etc/imunify-agent-proxy/jwt-secret.oldzimunify-agent-proxy)daysc tjt}|j}n#t$rd}YnwxYwt j|z t kS)Ng) osstatJWT_SECRET_PATHst_mtimeFileNotFoundErrorrnow timestampSECRET_EXPIRATION_TTL total_seconds)rrs Y/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/proxy_auth.pyis_secret_expiredrs|!w''=     ""X-  - - / / 0s # 22cKtt} tdt jd}|jddd|dt||dttd t d S#t$r(}td |d Yd }~d Sd }~wwxYw) zRotate the proxy JWT secret on disk: backup current to .old and write a fresh 32-byte secret atomically. Invalidates the in-process cache so subsequent generate_token() calls read the new secret. zRotating proxy auth secret iT)modeparentsexist_oki)r)uidbackup permissionsz'Got error while rotating the secret: %s)exc_infoN)rrloggerinfosecrets token_bytesparentmkdirtouchrstrJWT_SECRET_PATH_OLDload_secret_from_file cache_clear Exceptionerror) secret_path stub_secretes r rotate_secretr5*s ''K  0111)"--   eTD IIIu%%%  *++      ))+++++     5q4           sB B:: C,C''C,returnc^ ttd5}|cdddS#1swxYwYdS#t$r"t dtt$r!}t d|d}~wwxYw)z.Load JWT secret from the configured file path.rbNzJWT secret file not found at %szFailed to read JWT secret: %s)openrreadstriprr%r1r0)fr4s rr.r.Cs /4 ( ( $A6688>>## $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $  6HHH  4a888 s9A&A  A AAAA4B, B''B,usernamedocrootctjtz}|||d} ddl}||t d}|S#t $r!}td|d}~wwxYw)z Generate a JWT token for the given username and docroots. Args: username: The username for the token docroot: document root paths the user has access to Returns: The JWT token string )expr> site_pathrNHS256) algorithmz Failed to generate JWT token: %s) rutcnowDEFAULT_TOKEN_EXPIRATIONjwtencoder.r0r%r1)r>r?exp_timeclaimsrGtokenr4s rgenerate_tokenrLQs  #;;H8' J JF    6#8#:#:g NN  7;;; s)A A8A33A8rKgidclK tj|}t||d{V}|dz }d|i}t|}t jt ||||d{Vtd||dS#t$r"} t d|| d} ~ wwxYw)z Create the auth.php file in the site's imunify-security directory. Args: site: WPSite instance token: JWT token string uid, gid: int used for file creation Nzauth.phprKz'Created auth.php file for site %s at %sz.Failed to create auth.php file for site %s: %s) pwdgetpwuidrr asyncio to_threadr r%r&r0r1) siterKr!rM user_infodata_dirauth_file_path auth_data php_contentr4s rcreate_auth_php_filerYmsL%% 4D)DDDDDDDD!J.e$ 3I>>  -                5t^       EtQOOO sBB B3B..B3rTc2K t|jt|j}t |||j|jd{Vtd|dS#t$r"}t d||d}~wwxYw)z Set up authentication for a site by creating JWT token and auth.php file. Args: site: WPSite instance user_info: pwd.struct_passwd data Nz.Successfully set up authentication for site %sz/Failed to set up authentication for site %s: %s) rLpw_namer,r?rYpw_uidpw_gidr%r&r0r1)rSrTrKr4s rsetup_site_authenticationr^s y0#dl2C2CDD" %)9+;           DdKKKKK  =tQ     sA$A** B4BB)#rQloggingrrOr'rr functoolsrpathlibrdefence360agent.utilsrdefence360agent.wordpress.utilsrr r getLogger__name__r%rFrr-PROXY_SERVICE_NAMErrr5bytesr.r,rLintrY struct_passwdr^rrls ((((((((000000  8 $ $$92...7?*! q)))       2 1 u    S338%C%3%4%%%%P& rkdefence360agent/wordpress/__pycache__/site_repository.cpython-311.opt-1.pyc0000644000000000000000000006355400000000000023727 0ustar r_jG ddlZddlZddlZddlmZddlmZmZmZddl m Z ddl m Z m Z ddlmZejeZedZded ee fd Zd ejd eefd Zd ee fd Zd ee fdZdee d dfdZded ee fdZde ded dfdZ d,dee d ee fdZ de ded dfdZ!de d dfdZ" d-de#dzde#dzde#d e$e#ee ffdZ%d e&e#e#ffdZ'd ee fd Z(d!eed ee fd"Z)d#Z*e ed$d%d&e*'de d e#fd(Z+d ee fd)Z,d ee fd*Z-de d dfd+Z.dS).N)Path)SqliteDatabaseOperationalErrorfn)retry_on)WPSite WordpressSite) PLUGIN_SLUGzD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3pathreturnc<ts;tdt tt St td|d}d|DS)a Get a list of WordPress sites that match the given path. Args: path: The path to search for WordPress sites. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A list of WPSite objects that match the path. -App detector database '%s' couldn't be found.a WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs all_wp_sites AS ( SELECT wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE 'a%' AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ) -- For each real_path, keep only the entry from the latest report SELECT real_path, domain, uid FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) c pg|]3}t|d|dt|d4Sr)docrootdomainuidrint.0rows ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/site_repository.py z%get_sites_by_path..II     s1vc!f#c!f++>>>   ) COMPONENTS_DB_PATHexistsloggererrorstrlistr execute_sqlfetchall)r cursors rget_sites_by_pathr(s  $ $ & & ; " # #   vv . / / ; ; ()-)   !!FD  ??$$   r user_infoctr|;tdt tt S|(tdt St td|jd}d| DS)aq Get a set of paths to WordPress sites belonging to a particular user. Paths are sorted by their length to make sure that the main site is the last one in the list. The data is pulled from the app-version-detector database. Args: user_info: The user info with ID to get sites for. Returns: A list of paths to WordPress sites. Nrz'No user info provided for getting sitesz WITH latest_reports AS ( SELECT MAX(id) as id, dir FROM report WHERE uid = a GROUP BY dir ) SELECT wp.real_path FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' GROUP BY wp.real_path ORDER BY length(wp.real_path) DESC cg|] }|d S)rrs rrz&get_sites_for_user..zs 0 0 0sCF 0 0 0r) rr r!r"r#r$rr%pw_uidr&)r)r's rget_sites_for_userr.Os  $ $ & &)*; ; " # #   vv  5   vv . / / ; ; '-    F& 1 0foo// 0 0 00rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of wp sites where imunify-security plugin is not installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is not installed. ra WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE NOT EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_-_' ) c ph|]3}t|d|dt|d4Srrrs r z+get_sites_without_plugin..rr rr r!r"r#setrr%r replacer&r's rget_sites_without_pluginr9}  $ $ & & ; " # #   uu . / / ; ;+ R0;/B3/L/LS+ + + --F\  ??$$   rct}dtjtjDfd|DS)a Get a set of WordPress sites where we need to install the plugin. This is determined by finding sites that don't have the plugin installed and are not already tracked in our database. Returns: A set of WPSite objects where the plugin needs to be installed. ch|] }|j Sr,rrrs rr4z'get_sites_to_install..s' rc&h|] }|jv |Sr,r=)rsexisting_docrootss rr4z'get_sites_to_install..s-   19.sH  +<x<'+    r)r insert_manyexecute)rFs rinsert_installed_sitesrOsP     giiiiirlatest_versionc|stdgSdtjtjtj|kDS)a Get a list of WordPress sites that have outdated plugin versions. Args: latest_version: The latest available plugin version to compare against. Returns: A list of WPSite objects that have versions older than latest_version. z8Cannot get outdated sites without a valid latest versionc6g|]}tj|Sr,rfrom_wordpress_siter>s rrz&get_outdated_sites..3     "1%%   r)r!r"r rCwhererJis_nullrI)rPs rget_outdated_sitesrXs  F      %''--  - 5 5 7 7  !^ 3     rrL timestampctd||tj|tj|jkdS)z Mark a WordPress site as manually deleted in the database. Args: site: The WPSite object to mark as deleted timestamp: The timestamp when the site was deleted z:Mark site %s as manually deleted at %s (WP-Plugin removed)rJNr!infor updaterVrrN)rLrYs rmark_site_as_manually_deletedr_ s\ KKD  ;;; }$ 4 5 5 rfreshly_installed_sitesc2t}d|D}dtjtjD|t z}|r|d|Dz}fd|DS)a Get a set of WordPress sites that should be marked as manually deleted. These are sites that are in our database but no longer have the plugin installed. Args: freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Returns: set[WPSite]: A set of WordPress sites that should be marked as manually deleted ch|] }|j Sr,r=rrAs rr4z8get_sites_to_mark_as_manually_deleted..-sGGGQqyGGGrcBi|]}|jtj|Sr,)rrrTr>s r z9get_sites_to_mark_as_manually_deleted..0s7  6-a00rch|] }|j Sr,r=rcs rr4z8get_sites_to_mark_as_manually_deleted..<sHHH1QYHHHrc h|] }| Sr,r,)rdactive_db_sitess rr4z8get_sites_to_mark_as_manually_deleted..>s 9 9 91OA  9 9 9r)r9r rCrVrJrWr6)r`rDdocroots_without_plugindocroots_to_markris @r%get_sites_to_mark_as_manually_deletedrls455GG2FGGG%''--  - 5 5 7 7  O/_1E1EEIHH0GHHHH 9 9 9 9(8 9 9 99rrIctj|tj|jkdS)z Update the version of a WordPress site in the database. Args: site: The WPSite object to update version: The new version to set )rIN)r r^rVrrN)rLrIs rupdate_site_versionrnAsA)))//- giiiiirctj|j|jtj|jkdS)z Update the domain and uid of a WordPress site in the database to match what AVD currently reports. Args: site: The WPSite object with the current domain and uid from AVD. )rrN)r r^rrrVrrNrLs rupdate_site_identityrqNsG :::@@- giiiiirrlimitoffsetc~tjtjd}|#|tj|k}|}|||}|dkr||}d|D}||fS)a/ Get active installed WordPress sites with optional filtering and pagination. Args: uid: Optional user ID to filter sites by owner limit: Maximum number of sites to return offset: Number of sites to skip Returns: Tuple of (total_count, paginated_sites) TNrc6g|]}tj|Sr,rSrKs rrz1get_installed_sites_paginated..xs# @ @ @$V ' - - @ @ @r) r rCrVrJrWrcountrrrs)rrrrsquery total_countrFs rget_installed_sites_paginatedry[s  " " ( ()11$77  E  M-455++--K  E"" zz V$$ @ @% @ @ @E  rcttjtjtjtjdtj d tj }d|DS)aK Count active installed WordPress sites per owner uid in one query. Mirrors get_installed_sites_paginated's active-site filter (manually_deleted_at IS NULL). Uids with no active sites are absent from the result rather than mapped to 0. Returns: Mapping of uid -> number of active installed sites. rvTc,i|]}|d|dS)rrvr,rs rrez0count_installed_sites_by_uid..s" 6 6 6CJG 6 6 6r) r rCrrCOUNTraliasrVrJrWgroup_bydicts)rws rcount_installed_sites_by_uidr|s    H]* + + 1 1' : :   }088>> ? ? -# $ $   7 6 6 6 66rc(t\}}|S)z Get a list of active installed WordPress sites. These are sites that haven't been marked as manually deleted. Returns: A list of WPSite objects representing non-deleted sites. )ry)r1rFs rget_installed_sitesrs-..HAu Lrdomainsc|sgSdtjtjdtj|DS)z Get active installed WordPress sites filtered by domain names. Args: domains: List of domain names to filter by Returns: List of WPSite objects matching the given domains c6g|]}tj|Sr,rSr>s rrz2get_installed_sites_by_domains..rUrT)r rCrVrJrWrin_)rs rget_installed_sites_by_domainsrss    %''--  - 5 5d ; ;  $ $W - -     rc>Ktjdd{VdS)Ng?)asynciosleep) exceptionattempts rsleep_on_errorrs. -  rTF) max_triessilentlogon_errorctjtj|jkS)a" Delete a WordPress site from the database with retry logic. Will retry up to 3 times on database operational errors with 0.5s delay between attempts. Args: site: The WPSite object to delete Returns: The number of rows affected by the delete operation )r deleterVrrNrps r delete_siters5&  }$ 4 5 5 rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of WordPress sites where the imunify-security plugin is installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is installed. ra WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_r0r1r2c ph|]3}t|d|dt|d4Srrrs rr4z(get_sites_with_plugin..rrr5r8s rget_sites_with_pluginrr:rct}dtjtjtjdDfd|DS)aI Get a set of WordPress sites that should be adopted. These are sites where the plugin is installed but either: - Not tracked in our database (e.g., copied/migrated sites) - Flagged as manually removed (from past bugs or manual reinstall) Returns: A set of WPSite objects that should be adopted. ch|] }|j Sr,r=r>s rr4z%get_sites_to_adopt..'s*  rTc&h|] }|jv |Sr,r=)rrAtracked_docrootss rr4z%get_sites_to_adopt...s& N N N!AI=M,M,MA,M,M,Mr)rr rCrrVrJrW)sites_with_pluginrs @rget_sites_to_adoptrs.//%m&;<<BB  - 5 5d ; ;   O N N N( N N NNrctd|tjdtj|jkdS)z Clear the manually_deleted_at flag for a WordPress site. This is used when adopting a site that was previously marked as manually deleted. Args: site: The WPSite object to clear the flag for zrs 7777777777******AAAAAAAA;;;;;;  8 $ $TJ :C:DL::::z+1#"3+1S +1+1+1+1\E#f+EEEEPc&k$#f+$2stF|25T*,0!:!: [!:[!:!:!:!:H f s t     v $     t :  3V  B7d38n7777. T&\    DI$v,,    f$Es6{EEEEPOCKOOOO.frdefence360agent/wordpress/__pycache__/site_repository.cpython-311.pyc0000644000000000000000000006355400000000000022770 0ustar r_jG ddlZddlZddlZddlmZddlmZmZmZddl m Z ddl m Z m Z ddlmZejeZedZded ee fd Zd ejd eefd Zd ee fd Zd ee fdZdee d dfdZded ee fdZde ded dfdZ d,dee d ee fdZ de ded dfdZ!de d dfdZ" d-de#dzde#dzde#d e$e#ee ffdZ%d e&e#e#ffdZ'd ee fd Z(d!eed ee fd"Z)d#Z*e ed$d%d&e*'de d e#fd(Z+d ee fd)Z,d ee fd*Z-de d dfd+Z.dS).N)Path)SqliteDatabaseOperationalErrorfn)retry_on)WPSite WordpressSite) PLUGIN_SLUGzD/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3pathreturnc<ts;tdt tt St td|d}d|DS)a Get a list of WordPress sites that match the given path. Args: path: The path to search for WordPress sites. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A list of WPSite objects that match the path. -App detector database '%s' couldn't be found.a WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs all_wp_sites AS ( SELECT wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE 'a%' AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ) -- For each real_path, keep only the entry from the latest report SELECT real_path, domain, uid FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) c pg|]3}t|d|dt|d4Sr)docrootdomainuidrint.0rows ^/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/site_repository.py z%get_sites_by_path..II     s1vc!f#c!f++>>>   ) COMPONENTS_DB_PATHexistsloggererrorstrlistr execute_sqlfetchall)r cursors rget_sites_by_pathr(s  $ $ & & ; " # #   vv . / / ; ; ()-)   !!FD  ??$$   r user_infoctr|;tdt tt S|(tdt St td|jd}d| DS)aq Get a set of paths to WordPress sites belonging to a particular user. Paths are sorted by their length to make sure that the main site is the last one in the list. The data is pulled from the app-version-detector database. Args: user_info: The user info with ID to get sites for. Returns: A list of paths to WordPress sites. Nrz'No user info provided for getting sitesz WITH latest_reports AS ( SELECT MAX(id) as id, dir FROM report WHERE uid = a GROUP BY dir ) SELECT wp.real_path FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' GROUP BY wp.real_path ORDER BY length(wp.real_path) DESC cg|] }|d S)rrs rrz&get_sites_for_user..zs 0 0 0sCF 0 0 0r) rr r!r"r#r$rr%pw_uidr&)r)r's rget_sites_for_userr.Os  $ $ & &)*; ; " # #   vv  5   vv . / / ; ; '-    F& 1 0foo// 0 0 00rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of wp sites where imunify-security plugin is not installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is not installed. ra WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE NOT EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_-_' ) c ph|]3}t|d|dt|d4Srrrs r z+get_sites_without_plugin..rr rr r!r"r#setrr%r replacer&r's rget_sites_without_pluginr9}  $ $ & & ; " # #   uu . / / ; ;+ R0;/B3/L/LS+ + + --F\  ??$$   rct}dtjtjDfd|DS)a Get a set of WordPress sites where we need to install the plugin. This is determined by finding sites that don't have the plugin installed and are not already tracked in our database. Returns: A set of WPSite objects where the plugin needs to be installed. ch|] }|j Sr,rrrs rr4z'get_sites_to_install..s' rc&h|] }|jv |Sr,r=)rsexisting_docrootss rr4z'get_sites_to_install..s-   19.sH  +<x<'+    r)r insert_manyexecute)rFs rinsert_installed_sitesrOsP     giiiiirlatest_versionc|stdgSdtjtjtj|kDS)a Get a list of WordPress sites that have outdated plugin versions. Args: latest_version: The latest available plugin version to compare against. Returns: A list of WPSite objects that have versions older than latest_version. z8Cannot get outdated sites without a valid latest versionc6g|]}tj|Sr,rfrom_wordpress_siter>s rrz&get_outdated_sites..3     "1%%   r)r!r"r rCwhererJis_nullrI)rPs rget_outdated_sitesrXs  F      %''--  - 5 5 7 7  !^ 3     rrL timestampctd||tj|tj|jkdS)z Mark a WordPress site as manually deleted in the database. Args: site: The WPSite object to mark as deleted timestamp: The timestamp when the site was deleted z:Mark site %s as manually deleted at %s (WP-Plugin removed)rJNr!infor updaterVrrN)rLrYs rmark_site_as_manually_deletedr_ s\ KKD  ;;; }$ 4 5 5 rfreshly_installed_sitesc2t}d|D}dtjtjD|t z}|r|d|Dz}fd|DS)a Get a set of WordPress sites that should be marked as manually deleted. These are sites that are in our database but no longer have the plugin installed. Args: freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Returns: set[WPSite]: A set of WordPress sites that should be marked as manually deleted ch|] }|j Sr,r=rrAs rr4z8get_sites_to_mark_as_manually_deleted..-sGGGQqyGGGrcBi|]}|jtj|Sr,)rrrTr>s r z9get_sites_to_mark_as_manually_deleted..0s7  6-a00rch|] }|j Sr,r=rcs rr4z8get_sites_to_mark_as_manually_deleted..<sHHH1QYHHHrc h|] }| Sr,r,)rdactive_db_sitess rr4z8get_sites_to_mark_as_manually_deleted..>s 9 9 91OA  9 9 9r)r9r rCrVrJrWr6)r`rDdocroots_without_plugindocroots_to_markris @r%get_sites_to_mark_as_manually_deletedrls455GG2FGGG%''--  - 5 5 7 7  O/_1E1EEIHH0GHHHH 9 9 9 9(8 9 9 99rrIctj|tj|jkdS)z Update the version of a WordPress site in the database. Args: site: The WPSite object to update version: The new version to set )rIN)r r^rVrrN)rLrIs rupdate_site_versionrnAsA)))//- giiiiirctj|j|jtj|jkdS)z Update the domain and uid of a WordPress site in the database to match what AVD currently reports. Args: site: The WPSite object with the current domain and uid from AVD. )rrN)r r^rrrVrrNrLs rupdate_site_identityrqNsG :::@@- giiiiirrlimitoffsetc~tjtjd}|#|tj|k}|}|||}|dkr||}d|D}||fS)a/ Get active installed WordPress sites with optional filtering and pagination. Args: uid: Optional user ID to filter sites by owner limit: Maximum number of sites to return offset: Number of sites to skip Returns: Tuple of (total_count, paginated_sites) TNrc6g|]}tj|Sr,rSrKs rrz1get_installed_sites_paginated..xs# @ @ @$V ' - - @ @ @r) r rCrVrJrWrcountrrrs)rrrrsquery total_countrFs rget_installed_sites_paginatedry[s  " " ( ()11$77  E  M-455++--K  E"" zz V$$ @ @% @ @ @E  rcttjtjtjtjdtj d tj }d|DS)aK Count active installed WordPress sites per owner uid in one query. Mirrors get_installed_sites_paginated's active-site filter (manually_deleted_at IS NULL). Uids with no active sites are absent from the result rather than mapped to 0. Returns: Mapping of uid -> number of active installed sites. rvTc,i|]}|d|dS)rrvr,rs rrez0count_installed_sites_by_uid..s" 6 6 6CJG 6 6 6r) r rCrrCOUNTraliasrVrJrWgroup_bydicts)rws rcount_installed_sites_by_uidr|s    H]* + + 1 1' : :   }088>> ? ? -# $ $   7 6 6 6 66rc(t\}}|S)z Get a list of active installed WordPress sites. These are sites that haven't been marked as manually deleted. Returns: A list of WPSite objects representing non-deleted sites. )ry)r1rFs rget_installed_sitesrs-..HAu Lrdomainsc|sgSdtjtjdtj|DS)z Get active installed WordPress sites filtered by domain names. Args: domains: List of domain names to filter by Returns: List of WPSite objects matching the given domains c6g|]}tj|Sr,rSr>s rrz2get_installed_sites_by_domains..rUrT)r rCrVrJrWrin_)rs rget_installed_sites_by_domainsrss    %''--  - 5 5d ; ;  $ $W - -     rc>Ktjdd{VdS)Ng?)asynciosleep) exceptionattempts rsleep_on_errorrs. -  rTF) max_triessilentlogon_errorctjtj|jkS)a" Delete a WordPress site from the database with retry logic. Will retry up to 3 times on database operational errors with 0.5s delay between attempts. Args: site: The WPSite object to delete Returns: The number of rows affected by the delete operation )r deleterVrrNrps r delete_siters5&  }$ 4 5 5 rcbts;tdt tt St tdtj ddd}d| DS)a Get a set of WordPress sites where the imunify-security plugin is installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is installed. ra WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_r0r1r2c ph|]3}t|d|dt|d4Srrrs rr4z(get_sites_with_plugin..rrr5r8s rget_sites_with_pluginrr:rct}dtjtjtjdDfd|DS)aI Get a set of WordPress sites that should be adopted. These are sites where the plugin is installed but either: - Not tracked in our database (e.g., copied/migrated sites) - Flagged as manually removed (from past bugs or manual reinstall) Returns: A set of WPSite objects that should be adopted. ch|] }|j Sr,r=r>s rr4z%get_sites_to_adopt..'s*  rTc&h|] }|jv |Sr,r=)rrAtracked_docrootss rr4z%get_sites_to_adopt...s& N N N!AI=M,M,MA,M,M,Mr)rr rCrrVrJrW)sites_with_pluginrs @rget_sites_to_adoptrs.//%m&;<<BB  - 5 5d ; ;   O N N N( N N NNrctd|tjdtj|jkdS)z Clear the manually_deleted_at flag for a WordPress site. This is used when adopting a site that was previously marked as manually deleted. Args: site: The WPSite object to clear the flag for zrs 7777777777******AAAAAAAA;;;;;;  8 $ $TJ :C:DL::::z+1#"3+1S +1+1+1+1\E#f+EEEEPc&k$#f+$2stF|25T*,0!:!: [!:[!:!:!:!:H f s t     v $     t :  3V  B7d38n7777. T&\    DI$v,,    f$Es6{EEEEPOCKOOOO.frdefence360agent/wordpress/__pycache__/telemetry.cpython-311.opt-1.pyc0000644000000000000000000000204600000000000022463 0ustar r_j\ddlZddlmZddlmZejeZddededefdZ dS) N) MessageType)WPSiteeventsiteversionc K|d}|tj||j|j|j|d{VdS)Nz1.0.0)rdomain site_pathuserplugin_version)process_messagerWordpressPluginTelemetryr docrootuid)sinkrrrs X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/telemetry.py send_eventrsz   ,;l"      )N) logging"defence360agent.contracts.messagesrdefence360agent.model.wordpressr getLogger__name__loggerstrrrrrs|::::::222222  8 $ $  # V c      rdefence360agent/wordpress/__pycache__/telemetry.cpython-311.pyc0000644000000000000000000000204600000000000021524 0ustar r_j\ddlZddlmZddlmZejeZddededefdZ dS) N) MessageType)WPSiteeventsiteversionc K|d}|tj||j|j|j|d{VdS)Nz1.0.0)rdomain site_pathuserplugin_version)process_messagerWordpressPluginTelemetryr docrootuid)sinkrrrs X/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/telemetry.py send_eventrsz   ,;l"      )N) logging"defence360agent.contracts.messagesrdefence360agent.model.wordpressr getLogger__name__loggerstrrrrrs|::::::222222  8 $ $  # V c      rdefence360agent/wordpress/__pycache__/utils.cpython-311.opt-1.pyc0000644000000000000000000006602200000000000021615 0ustar r_jXddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z ddl m Z m Z ddlmZddlmZddlmZmZddlmZdd lmZdd lmZdd lmZmZmZm Z m!Z!m"Z"dd l#m$Z$m%Z%dd l&m'Z'ddl(m)Z)ddl*m+Z+dZ,dZ-e!j.dddZ/e dZ0e dZ1e dZ2e3dZ4ej5e6Z7de8de9fdZ:edde;e9ee d"#de?fd$Z@d%ZAd&e9d'ee9de9fd?ZOd@e9de9fdAZPdBe;de9fdCZQd9e9de;fdDZRdEed:eMd;eMds$ <0    r$)balancedstrictmonitorvaluereturncFt|tr |tvr|SdS)uCoerce a config-read preset value to a canonical preset string. Returns "balanced" for anything outside _VALID_PRESETS — including None, non-strings, and hand-edited values like "extreme" or "BALANCED". The agent always writes lowercase canonical values, so a non-canonical read indicates either a manual edit or a future preset that this version doesn't recognise; "balanced" is the safe fallback in both cases. r.) isinstancestr_VALID_PRESETS)r1s r%_validate_presetr7Ls*%%>"9"9 :r$<)ttlcKt}|d{V}tt}|D]%\}}|D]}|||&|S)zN Get a mapping of docroots to their associated domains, with caching. N)r get_domain_pathsrlistitemsappend) hosting_panel panel_paths docroot_mapdomaindocrootsdocroots r%r;r;[s !NNM%6688888888Kd##K'--//00 0 0G  ' ' / / / / 0 r$php_pathrDc0tt||gS)zGet wp cli common command list)r5r)rErDs r% wp_wrapperrGis # $ $h 88r$)maxsizectjtr$tjttjst Stjtdgdd}|j dkrt S|j d}t |ddS)z)Get the list of users enabled for CageFS.z--list-enabledT)capture_outputtextr rHN) ospathisfileCAGEFS_CTL_PATHaccessX_OKset subprocessrun returncodestdoutstripsplit)resultliness r%get_cagefs_enabled_usersr]ns 7>>/ * *")33uu ^ *+DtFAuu M   ! ! ' ' - -E uQRRy>>r$c8tdS)z-Clear the cache for get_cagefs_enabled_users.N)r] cache_clearr#r$r%$clear_get_cagefs_enabled_users_cacher`s((*****r$usernameargsc|tvrTtjtr0tjttjr td|g|Sddd|dtj|gS)zNBuild the necessary command to run the given cmdline args with specified user.z--no-io-and-memory-limitsuz-sz /bin/bashz-c) r]rNrOrPCAGEFS_ENTER_PATHrRrSshlexjoin)rarbs r%build_command_for_userrhs+---- 7>>+ , ,  rw2 2  "*      4  r$domain_to_excludecxKtd{V}||g}fd|DS)z Get all domains associated with a given document root, excluding one domain. It's panel-agnostic and uses a cached mapping. Nc g|] }|k| Sr#r#).0rBris r% z+get_domains_for_docroot..s$ L L Lv:K0K0KF0K0K0Kr$)r;r")rDrirA all_domainss ` r%get_domains_for_docrootrosS)********K//'2..K L L L L L L LLr$sitecP Kddlm}m}|| dtdttf fd }||j}|r|St |j|jd{V}|D]}||}|r|cStd|jd ) z/Determine PHP binary path for the given WPSite.r)get_domains_php_infoget_installed_php_versionsrBr2c|}|r|dkrdS|d}|sdSD]2}|d|kr|dcS3dS)Nradisplay_version identifierbin)r")rB domain_infophp_display_version php_versiondomains_php_infoinstalled_php_versionsras r%find_php_binary_for_domainz7get_php_binary_path..find_php_binary_for_domains&**622  kooj99XEE4)oo.?@@" 41 . .K|,,0CCC"u-----Dtr$)riNz+PHP binary was not identified for docroot: z , username: ) clcommon.cpapirrrsr5r rBrorDr) rprarrrsr}php_binary_pathdomainsrBr{r|s ` @@r%get_php_binary_pathrsX ,+--7799 3 8C=        10==O,  G##44V<<  #" " " " #  dl       r$cttdgSt|\}}|S)z Get malware history for the specified user. This is an equivalent of calling `imunify360-agent malware history list --user {username}`. Returns empty list if imav malware module is not available. Nz>imav.malwarelib not available, returning empty malware history)user)rloggerdebugmalicious_list)ra max_counthitss r%get_malware_historyrsJ L    "11x1@@Y Kr$c Kt}t}t}|||tdiS||}||j|hd{V\}}|siS||dd}|dS)z Get the last scan for the specified user. This is an equivalent of calling `imunify360-agent malware user list --user {username}`. Returns empty dict if imav malware module is not available. Nz8imav.malwarelib not available, returning empty last scan)match scan_dateT)descr)r&r*r-rrget_scans_from_paths)sinkraqueue_supervisor_clsr)sort_user_listqueue_userss r% get_last_scanrs122)++O'))N$  "  ! F     & &E$_ "8*HAu  N5+D 9 9 9E 8Or$c h tj}|tjkrF||ddd}||kr|t dz }|S|tjkrt||dzdzz dzdz}|dkr |j |krd}|t |z}||dddS|tj krddl m  fd}|j |kp5|j |ko |j |kp| |j|jdk} | r7||j|j|\} } ||| | |ddd}n|||ddd }|Sd S) a Calculate the next scan timestamp based on schedule configuration. Args: interval: Scan interval (DAY, WEEK, MONTH, or NONE) hour: Hour of day to run scan (0-23) day_of_month: Day of month to run scan (1-31) day_of_week: Day of week to run scan (0-6, where 0=Sunday) Returns: Timestamp of next scan, or None if interval is NONE r)hourminutesecond microsecondrH)days) monthrangec||}}|dz }|dkrd}|dz } ||d}||kr||fS|dz }|dkrd}|dz }/)z;Find the next month that has at least given number of days.rH r#)yearmonthr current_year current_month days_in_monthrs r%find_next_suitable_monthz?calculate_next_scan_timestamp..find_next_suitable_month/s*.-L Q Mr!! ! !  & * < G G J =(('66"  2%%$%M A%L &r$)dayrrrrrr)rrrrrN)rutcnowIntervalDAYreplacer timestampWEEKweekdayrMONTHcalendarrrrr) intervalr day_of_month day_of_weektoday next_scan days_aheadnext_scan_datershould_advance_month next_year next_monthrs @r%calculate_next_scan_timestamprs9 O  E8<MM "  I   *** *I""$$$8=  "U]]__q%8A$==AQF ??uzT11J !;!;!;;%%aq&  )++ 8>!!'''''' & & & & &0 I $ E \)@ejD.@ EjjU[AA!DD   $<$< EK%% !Iz#]]  +NN#]]  +N'')))w"!r$c:Kttd{VS)zFetch installed versions of Imunify packages. Returns a dict mapping package name to version string, with None for packages that are not installed. Intended to be called once per sync cycle (not per site). N)rrr#r$r%get_imunify_package_versionsrjs)&&;<< < < < < < <>.%*<<%%%K   #      )" # #  |  #                       sA88A<?A<json_strcV|ddddS)a Escape a JSON string for embedding inside a PHP single-quoted string. PHP single-quoted strings only recognise two escape sequences: ``\\`` (literal backslash) and ``\'`` (literal single quote). All other backslash sequences are kept verbatim. That means we must double every ``\`` *before* we escape ``'``, otherwise PHP will consume JSON backslashes (e.g. ``\\s`` in JSON becomes ``\s`` after PHP parsing, which is not a valid JSON escape). \\\'\'r)rs r%)_escape_json_for_php_single_quoted_stringrs*   D& ) ) 1 1#u = ==r$escapedcV|ddddS)z\ Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`. rrrrr)rs r% _unescape_php_single_quoted_jsonr,s( ??5# & & . .vt < < )z .htaccessz index.phpz index.htmlrrrN)r=r)rrrrprotection_filesfilenamerrs r%#ensure_directory_listing_protectionr isxLDD .3355  'x' ) wCS       r$ user_infoc *Kddlm}||d{V}d} t|}n#t$rt |jddt|g}t|d{V t|}n[#t$rN}|j tj tj fvrtd|d|td |d ||d}~wwxYwd }YnEt$r9}|j tj tj fvrtd|d|d}~wwxYw |rtj|d t!||j|j| tj|n#tj|wxYw|S)aEnsure the site's data directory exists with correct permissions. The directory is opened with symlink protection after creation (or if it already exists) to obtain a stable file descriptor. All subsequent operations use that descriptor. Args: site: WordPress site user_info: User information from pwd Returns: Path to data directory Raises: Exception: If the data directory is a symlink or cannot be created r)cliNFmkdirz-pzData directory z is a symlink, skipping.zFailed to open data directory z: Tir )defence360agent.wordpressr get_data_dirrFileNotFoundErrorrhpw_namer5rOSErrorerrnoELOOPENOTDIR ExceptionrNchmodr rpw_gidclose)rpr rr newly_createdrcommandexcs r%ensure_site_data_directoryr s%&.-----%%d++++++++HM%h// )   dCMM *            )(33FF   yU[%-888HhHHHBBBSBB     9em4 4 4D(DDD     $ HVU # # #+ $( (8      OsM7AD-<B  D- C$A CC$$D-+ D-44D((D-14E::F)N)VrrloggingrNpwdrfrU collectionsrrr functoolsrrpathlibrtypingr defence360agent.contracts.configr r r!defence360agent.contracts.licenser +defence360agent.subsys.panels.hosting_panelr #defence360agent.subsys.panels.pleskrdefence360agent.utilsrrrrrrdefence360agent.utils.fd_opsrrdefence360agent.model.wordpressr#defence360agent.wordpress.constantsr#defence360agent.wordpress.exceptionrrerQr"rr&r*r- frozensetr6 getLogger__name__robjectr5r7dictr<r;rGrTr]r`rhrorrrrrfloatrrrintrrrrrr  struct_passwdr r#r$r%r8s?  ######((((((((&&&&&&&&988888DDDDDD555555HGGGGGGG222222CCCCCC8888881' X\ "t <==  8 $ $ F s    R S$s)^ 4    99s9t9999  1#"+++ S. M  M%( M #Y M M M M)F)c)hsm))))X#$"Ba*a*a*H=DcDj,A==== .2 @@@@@  @  @ 3d ?#d* @ @@@@Fst<#C#D####NJN      "%  ,/   > > > > >=c=c====2 # $    :   #& 36      0> > .> >>>>>>r$defence360agent/wordpress/__pycache__/utils.cpython-311.pyc0000644000000000000000000006602200000000000020656 0ustar r_jXddlZddlZddlZddlZddlZddlZddlZddlmZddl m Z m Z ddl m Z m Z ddlmZddlmZddlmZmZddlmZdd lmZdd lmZdd lmZmZmZm Z m!Z!m"Z"dd l#m$Z$m%Z%dd l&m'Z'ddl(m)Z)ddl*m+Z+dZ,dZ-e!j.dddZ/e dZ0e dZ1e dZ2e3dZ4ej5e6Z7de8de9fdZ:edde;e9ee d"#de?fd$Z@d%ZAd&e9d'ee9de9fd?ZOd@e9de9fdAZPdBe;de9fdCZQd9e9de;fdDZRdEed:eMd;eMds$ <0    r$)balancedstrictmonitorvaluereturncFt|tr |tvr|SdS)uCoerce a config-read preset value to a canonical preset string. Returns "balanced" for anything outside _VALID_PRESETS — including None, non-strings, and hand-edited values like "extreme" or "BALANCED". The agent always writes lowercase canonical values, so a non-canonical read indicates either a manual edit or a future preset that this version doesn't recognise; "balanced" is the safe fallback in both cases. r.) isinstancestr_VALID_PRESETS)r1s r%_validate_presetr7Ls*%%>"9"9 :r$<)ttlcKt}|d{V}tt}|D]%\}}|D]}|||&|S)zN Get a mapping of docroots to their associated domains, with caching. N)r get_domain_pathsrlistitemsappend) hosting_panel panel_paths docroot_mapdomaindocrootsdocroots r%r;r;[s !NNM%6688888888Kd##K'--//00 0 0G  ' ' / / / / 0 r$php_pathrDc0tt||gS)zGet wp cli common command list)r5r)rErDs r% wp_wrapperrGis # $ $h 88r$)maxsizectjtr$tjttjst Stjtdgdd}|j dkrt S|j d}t |ddS)z)Get the list of users enabled for CageFS.z--list-enabledT)capture_outputtextr rHN) ospathisfileCAGEFS_CTL_PATHaccessX_OKset subprocessrun returncodestdoutstripsplit)resultliness r%get_cagefs_enabled_usersr]ns 7>>/ * *")33uu ^ *+DtFAuu M   ! ! ' ' - -E uQRRy>>r$c8tdS)z-Clear the cache for get_cagefs_enabled_users.N)r] cache_clearr#r$r%$clear_get_cagefs_enabled_users_cacher`s((*****r$usernameargsc|tvrTtjtr0tjttjr td|g|Sddd|dtj|gS)zNBuild the necessary command to run the given cmdline args with specified user.z--no-io-and-memory-limitsuz-sz /bin/bashz-c) r]rNrOrPCAGEFS_ENTER_PATHrRrSshlexjoin)rarbs r%build_command_for_userrhs+---- 7>>+ , ,  rw2 2  "*      4  r$domain_to_excludecxKtd{V}||g}fd|DS)z Get all domains associated with a given document root, excluding one domain. It's panel-agnostic and uses a cached mapping. Nc g|] }|k| Sr#r#).0rBris r% z+get_domains_for_docroot..s$ L L Lv:K0K0KF0K0K0Kr$)r;r")rDrirA all_domainss ` r%get_domains_for_docrootrosS)********K//'2..K L L L L L L LLr$sitecP Kddlm}m}|| dtdttf fd }||j}|r|St |j|jd{V}|D]}||}|r|cStd|jd ) z/Determine PHP binary path for the given WPSite.r)get_domains_php_infoget_installed_php_versionsrBr2c|}|r|dkrdS|d}|sdSD]2}|d|kr|dcS3dS)Nradisplay_version identifierbin)r")rB domain_infophp_display_version php_versiondomains_php_infoinstalled_php_versionsras r%find_php_binary_for_domainz7get_php_binary_path..find_php_binary_for_domains&**622  kooj99XEE4)oo.?@@" 41 . .K|,,0CCC"u-----Dtr$)riNz+PHP binary was not identified for docroot: z , username: ) clcommon.cpapirrrsr5r rBrorDr) rprarrrsr}php_binary_pathdomainsrBr{r|s ` @@r%get_php_binary_pathrsX ,+--7799 3 8C=        10==O,  G##44V<<  #" " " " #  dl       r$cttdgSt|\}}|S)z Get malware history for the specified user. This is an equivalent of calling `imunify360-agent malware history list --user {username}`. Returns empty list if imav malware module is not available. Nz>imav.malwarelib not available, returning empty malware history)user)rloggerdebugmalicious_list)ra max_counthitss r%get_malware_historyrsJ L    "11x1@@Y Kr$c Kt}t}t}|||tdiS||}||j|hd{V\}}|siS||dd}|dS)z Get the last scan for the specified user. This is an equivalent of calling `imunify360-agent malware user list --user {username}`. Returns empty dict if imav malware module is not available. Nz8imav.malwarelib not available, returning empty last scan)match scan_dateT)descr)r&r*r-rrget_scans_from_paths)sinkraqueue_supervisor_clsr)sort_user_listqueue_userss r% get_last_scanrs122)++O'))N$  "  ! F     & &E$_ "8*HAu  N5+D 9 9 9E 8Or$c h tj}|tjkrF||ddd}||kr|t dz }|S|tjkrt||dzdzz dzdz}|dkr |j |krd}|t |z}||dddS|tj krddl m  fd}|j |kp5|j |ko |j |kp| |j|jdk} | r7||j|j|\} } ||| | |ddd}n|||ddd }|Sd S) a Calculate the next scan timestamp based on schedule configuration. Args: interval: Scan interval (DAY, WEEK, MONTH, or NONE) hour: Hour of day to run scan (0-23) day_of_month: Day of month to run scan (1-31) day_of_week: Day of week to run scan (0-6, where 0=Sunday) Returns: Timestamp of next scan, or None if interval is NONE r)hourminutesecond microsecondrH)days) monthrangec||}}|dz }|dkrd}|dz } ||d}||kr||fS|dz }|dkrd}|dz }/)z;Find the next month that has at least given number of days.rH r#)yearmonthr current_year current_month days_in_monthrs r%find_next_suitable_monthz?calculate_next_scan_timestamp..find_next_suitable_month/s*.-L Q Mr!! ! !  & * < G G J =(('66"  2%%$%M A%L &r$)dayrrrrrr)rrrrrN)rutcnowIntervalDAYreplacer timestampWEEKweekdayrMONTHcalendarrrrr) intervalr day_of_month day_of_weektoday next_scan days_aheadnext_scan_datershould_advance_month next_year next_monthrs @r%calculate_next_scan_timestamprs9 O  E8<MM "  I   *** *I""$$$8=  "U]]__q%8A$==AQF ??uzT11J !;!;!;;%%aq&  )++ 8>!!'''''' & & & & &0 I $ E \)@ejD.@ EjjU[AA!DD   $<$< EK%% !Iz#]]  +NN#]]  +N'')))w"!r$c:Kttd{VS)zFetch installed versions of Imunify packages. Returns a dict mapping package name to version string, with None for packages that are not installed. Intended to be called once per sync cycle (not per site). N)rrr#r$r%get_imunify_package_versionsrjs)&&;<< < < < < < <>.%*<<%%%K   #      )" # #  |  #                       sA88A<?A<json_strcV|ddddS)a Escape a JSON string for embedding inside a PHP single-quoted string. PHP single-quoted strings only recognise two escape sequences: ``\\`` (literal backslash) and ``\'`` (literal single quote). All other backslash sequences are kept verbatim. That means we must double every ``\`` *before* we escape ``'``, otherwise PHP will consume JSON backslashes (e.g. ``\\s`` in JSON becomes ``\s`` after PHP parsing, which is not a valid JSON escape). \\\'\'r)rs r%)_escape_json_for_php_single_quoted_stringrs*   D& ) ) 1 1#u = ==r$escapedcV|ddddS)z\ Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`. rrrrr)rs r% _unescape_php_single_quoted_jsonr,s( ??5# & & . .vt < < )z .htaccessz index.phpz index.htmlrrrN)r=r)rrrrprotection_filesfilenamerrs r%#ensure_directory_listing_protectionr isxLDD .3355  'x' ) wCS       r$ user_infoc *Kddlm}||d{V}d} t|}n#t$rt |jddt|g}t|d{V t|}n[#t$rN}|j tj tj fvrtd|d|td |d ||d}~wwxYwd }YnEt$r9}|j tj tj fvrtd|d|d}~wwxYw |rtj|d t!||j|j| tj|n#tj|wxYw|S)aEnsure the site's data directory exists with correct permissions. The directory is opened with symlink protection after creation (or if it already exists) to obtain a stable file descriptor. All subsequent operations use that descriptor. Args: site: WordPress site user_info: User information from pwd Returns: Path to data directory Raises: Exception: If the data directory is a symlink or cannot be created r)cliNFmkdirz-pzData directory z is a symlink, skipping.zFailed to open data directory z: Tir )defence360agent.wordpressr get_data_dirrFileNotFoundErrorrhpw_namer5rOSErrorerrnoELOOPENOTDIR ExceptionrNchmodr rpw_gidclose)rpr rr newly_createdrcommandexcs r%ensure_site_data_directoryr s%&.-----%%d++++++++HM%h// )   dCMM *            )(33FF   yU[%-888HhHHHBBBSBB     9em4 4 4D(DDD     $ HVU # # #+ $( (8      OsM7AD-<B  D- C$A CC$$D-+ D-44D((D-14E::F)N)VrrloggingrNpwdrfrU collectionsrrr functoolsrrpathlibrtypingr defence360agent.contracts.configr r r!defence360agent.contracts.licenser +defence360agent.subsys.panels.hosting_panelr #defence360agent.subsys.panels.pleskrdefence360agent.utilsrrrrrrdefence360agent.utils.fd_opsrrdefence360agent.model.wordpressr#defence360agent.wordpress.constantsr#defence360agent.wordpress.exceptionrrerQr"rr&r*r- frozensetr6 getLogger__name__robjectr5r7dictr<r;rGrTr]r`rhrorrrrrfloatrrrintrrrrrr  struct_passwdr r#r$r%r8s?  ######((((((((&&&&&&&&988888DDDDDD555555HGGGGGGG222222CCCCCC8888881' X\ "t <==  8 $ $ F s    R S$s)^ 4    99s9t9999  1#"+++ S. M  M%( M #Y M M M M)F)c)hsm))))X#$"Ba*a*a*H=DcDj,A==== .2 @@@@@  @  @ 3d ?#d* @ @@@@Fst<#C#D####NJN      "%  ,/   > > > > >=c=c====2 # $    :   #& 36      0> > .> >>>>>>r$defence360agent/wordpress/__pycache__/wp_rules.cpython-311.opt-1.pyc0000644000000000000000000001237500000000000022317 0ustar r_j dZddlZddlZddlZddlmZddlmZeje Z dZ dZ dede d edzfd Zd ed edzfd Zded edzfd Zded e fdZdS)zWordPress rules file management. This module provides utilities for loading and parsing wp-rules.yaml from the files.imunify360.com index system. Available for both AV and IM360 modes. N)Path)Indexz wp-rules.zipVERSIONindexfilenamereturnc4|D]N}|d|kr@t||d}|r|cSOtd|||jdS)z Find a file path from the index by filename. Args: index: files.Index object filename: Name of the file to find (e.g., WP_RULES_ZIP_FILENAME) Returns: Path to the file or None if not found nameurlz%s not found in %sN)itemsr localfilepathexistsloggererror files_pathtype)rritem file_paths W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/wp_rules.pyfind_file_in_indexrs !! <8 # #U00e==>>I!! !     LL%x1A1A%*1M1MNNN 4zip_pathc tj|d5}|d5}tj|}dddn #1swxYwYdddn #1swxYwYnJ#tjt tjf$r&}t d|Yd}~dSd}~wwxYwt|tst d|dS|S)z Extract and parse wp-rules.yaml from the zip file. Args: zip_path: Path to wp-rules.zip file Returns: Parsed YAML data as dict or None if extraction/parsing fails rz wp-rules.yamlNz,Failed to extract or parse wp-rules.yaml: %sz Invalid wp-rules.yaml format: %s) zipfileZipFileopenyaml safe_load BadZipFileKeyError YAMLErrorrr isinstancedict)rzip_file yaml_file rules_dataes rextract_wp_rules_yamlr),sb _Xs + + 7x// 79!^I66  7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7  $. 9 CQGGGttttt j$ ' ' 7DDDt s]A1A%A A%A A%A A% A1%A))A1,A)-A11!B8B33B8ct|t}|sdSt|}|sdStd|S)a Retrieve the latest WordPress rules and return them as a dictionary. Args: index: The files.Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data as a dictionary. If the wp-rules archive or data cannot be found or parsed, returns None. Note: This function returns the raw rules data. Callers that need to modify rules based on product mode (e.g., ANTIVIRUS_MODE) should do so after calling this function. Nz!Successfully parsed wp-rules.yaml)rWP_RULES_ZIP_FILENAMEr)rinfo)rrr's rget_wp_rules_datar-DsV""%)>??H t'x00J t KK3444 rc"t|t}|sdS |}td||S#t $r&}td|Yd}~dSd}~wwxYw)a# Retrieve the WordPress ruleset version string from the VERSION file. Args: index: The files.Index object used to locate the VERSION file. Returns: The version string from the VERSION file. If the VERSION file cannot be found or read, returns "NA". NAz&Successfully read wp-rules version: %szFailed to read VERSION file: %sN)rWP_RULES_VERSION_FILENAME read_textstriprr, Exceptionr)r version_pathversion_stringr(s rget_wp_ruleset_versionr6bs&e-FGGL t%//117799 r?s ''''''  8 $ $'%estd{*DTD[0Utd{<%Crdefence360agent/wordpress/__pycache__/wp_rules.cpython-311.pyc0000644000000000000000000001237500000000000021360 0ustar r_j dZddlZddlZddlZddlmZddlmZeje Z dZ dZ dede d edzfd Zd ed edzfd Zded edzfd Zded e fdZdS)zWordPress rules file management. This module provides utilities for loading and parsing wp-rules.yaml from the files.imunify360.com index system. Available for both AV and IM360 modes. N)Path)Indexz wp-rules.zipVERSIONindexfilenamereturnc4|D]N}|d|kr@t||d}|r|cSOtd|||jdS)z Find a file path from the index by filename. Args: index: files.Index object filename: Name of the file to find (e.g., WP_RULES_ZIP_FILENAME) Returns: Path to the file or None if not found nameurlz%s not found in %sN)itemsr localfilepathexistsloggererror files_pathtype)rritem file_paths W/opt/imunify360/venv/lib/python3.11/site-packages/defence360agent/wordpress/wp_rules.pyfind_file_in_indexrs !! <8 # #U00e==>>I!! !     LL%x1A1A%*1M1MNNN 4zip_pathc tj|d5}|d5}tj|}dddn #1swxYwYdddn #1swxYwYnJ#tjt tjf$r&}t d|Yd}~dSd}~wwxYwt|tst d|dS|S)z Extract and parse wp-rules.yaml from the zip file. Args: zip_path: Path to wp-rules.zip file Returns: Parsed YAML data as dict or None if extraction/parsing fails rz wp-rules.yamlNz,Failed to extract or parse wp-rules.yaml: %sz Invalid wp-rules.yaml format: %s) zipfileZipFileopenyaml safe_load BadZipFileKeyError YAMLErrorrr isinstancedict)rzip_file yaml_file rules_dataes rextract_wp_rules_yamlr),sb _Xs + + 7x// 79!^I66  7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7  $. 9 CQGGGttttt j$ ' ' 7DDDt s]A1A%A A%A A%A A% A1%A))A1,A)-A11!B8B33B8ct|t}|sdSt|}|sdStd|S)a Retrieve the latest WordPress rules and return them as a dictionary. Args: index: The files.Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data as a dictionary. If the wp-rules archive or data cannot be found or parsed, returns None. Note: This function returns the raw rules data. Callers that need to modify rules based on product mode (e.g., ANTIVIRUS_MODE) should do so after calling this function. Nz!Successfully parsed wp-rules.yaml)rWP_RULES_ZIP_FILENAMEr)rinfo)rrr's rget_wp_rules_datar-DsV""%)>??H t'x00J t KK3444 rc"t|t}|sdS |}td||S#t $r&}td|Yd}~dSd}~wwxYw)a# Retrieve the WordPress ruleset version string from the VERSION file. Args: index: The files.Index object used to locate the VERSION file. Returns: The version string from the VERSION file. If the VERSION file cannot be found or read, returns "NA". NAz&Successfully read wp-rules version: %szFailed to read VERSION file: %sN)rWP_RULES_VERSION_FILENAME read_textstriprr, Exceptionr)r version_pathversion_stringr(s rget_wp_ruleset_versionr6bs&e-FGGL t%//117799 r?s ''''''  8 $ $'%estd{*DTD[0Utd{<%Crdefence360agent/wordpress/bot_protection.py0000644000000000000000000001033300000000000016202 0ustar """Resolve the AI bot protection state actually applied on a WP site. Mirrors the precedence the imunify-security plugin applies at runtime (inc/App/Plugin.php::isBotProtectionActive, inc/App/Bot/Preset.php::resolve): a site-owner wp-config.php constant or bot-settings.php override wins over the hoster-written plugin_config.php default. Files are parsed, never executed. """ import logging import os import re import stat from pathlib import Path logger = logging.getLogger(__name__) VALID_PRESETS = ("balanced", "strict", "monitor") DEFAULT_PRESET = "balanced" # These config files live under a hosting user's document root and are read # by the root agent, so the read is defensive: no symlink follow, no FIFO # block, regular file owned by the site user only, and a small byte cap so a # hostile file (huge / /dev/zero) cannot OOM or stall the agent. _MAX_BYTES = 64 * 1024 # define('IMUNIFY_AI_BOT_PROTECTION', false) — the quote right after the name # keeps this from also matching the *_PRESET constant. _CONST_ENABLED = re.compile( r"""define\(\s*['"]IMUNIFY_AI_BOT_PROTECTION['"]\s*,\s*(true|false)\s*\)""", re.IGNORECASE, ) _CONST_PRESET = re.compile( r"define\(\s*['\"]IMUNIFY_AI_BOT_PROTECTION_PRESET['\"]\s*," r"\s*['\"](\w+)['\"]\s*\)", re.IGNORECASE, ) # bot-settings.php is a PHP `return array('enabled' => .., 'preset' => '..')`. _KV_ENABLED = re.compile( r"""['"]enabled['"]\s*=>\s*(true|false)""", re.IGNORECASE ) _KV_PRESET = re.compile( r"""['"]preset['"]\s*=>\s*['"](\w+)['"]""", re.IGNORECASE ) def _safe_read(path: Path, uid: int): """Read a small site-owner config file as root, defensively. Returns None (so the caller falls back to the hoster default) on a symlink, FIFO/device, a file not owned by the site user, or any I/O error. At most _MAX_BYTES are read. """ try: fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) except OSError: return None try: info = os.fstat(fd) if not stat.S_ISREG(info.st_mode) or info.st_uid != uid: return None return os.read(fd, _MAX_BYTES).decode("utf-8", errors="replace") except OSError: return None finally: os.close(fd) def _parse_wp_config(path: Path, uid: int): """Return (enabled, preset) from wp-config.php constants; each is None when the constant is absent or invalid.""" text = _safe_read(path, uid) if text is None: return None, None enabled = None match = _CONST_ENABLED.search(text) if match: enabled = match.group(1).lower() == "true" preset = None match = _CONST_PRESET.search(text) if match and match.group(1).lower() in VALID_PRESETS: preset = match.group(1).lower() return enabled, preset def _parse_bot_settings(path: Path, uid: int): """Return (enabled, preset) from the site-owner bot-settings.php. A missing/unreadable file means enabled with no explicit preset, matching the plugin's OptOutFlag default.""" text = _safe_read(path, uid) if text is None: return True, None match = _KV_ENABLED.search(text) enabled = match.group(1).lower() == "true" if match else True preset = None match = _KV_PRESET.search(text) if match and match.group(1).lower() in VALID_PRESETS: preset = match.group(1).lower() return enabled, preset def resolve_ai_bot_protection( docroot: str, data_dir: Path, uid: int, hoster_enabled: bool, hoster_preset: str, ): """Resolve the effective (enabled, preset) for a site. enabled: the wp-config constant (if set to false) force-disables; otherwise it is the AND of the hoster default and the site-owner flag. preset: first match of wp-config constant, bot-settings.php, hoster. """ const_enabled, const_preset = _parse_wp_config( Path(docroot) / "wp-config.php", uid ) bot_enabled, bot_preset = _parse_bot_settings( Path(data_dir) / "bot-settings.php", uid ) enabled = bool(hoster_enabled) and bot_enabled if const_enabled is False: enabled = False for candidate in (const_preset, bot_preset, hoster_preset): if candidate in VALID_PRESETS: return enabled, candidate return enabled, DEFAULT_PRESET defence360agent/wordpress/changelog_processor.py0000644000000000000000000003117700000000000017207 0ustar """Processor for WordPress rule disable/enable changelog files. The PHP WordPress plugin writes rule change actions to changelog.php when a user disables or enables protection rules from the WordPress admin panel. This module reads, parses, and applies those actions to the agent database. The changelog.php file uses the same format as incident files: None: # changelog.php uses the same format as incident files # (base64-encoded JSON lines wrapped in PHP), so we reuse the parser self.parser = IncidentFileParser() async def process_changelogs_for_sites( self, sites: list[WPSite], sink: MessageSink | None, ) -> list[WPSite]: """Process changelog.php for all given sites. Args: sites: WordPress sites to process. sink: MessageSink for sending correlation events. Returns: Sites whose disabled rules were affected (needing disabled-rules.php regeneration). """ affected: list[WPSite] = [] for site in sites: if await self._process_site(site, sink): affected.append(site) if affected: logger.info( "Changelog processing affected %d site(s)", len(affected), ) return affected async def _process_site( self, site: WPSite, sink: MessageSink | None, ) -> bool: """Process changelog.php for a single site. Args: site: WordPress site to process. sink: MessageSink for sending correlation events. Returns: True if the site's disabled rules were affected. """ try: data_dir = await get_data_dir(site) if not data_dir.exists(): return False changelog_path = data_dir / CHANGELOG_FILENAME if changelog_path.exists(): if await self._process_changelog_file( changelog_path, site, sink ): return True if self._is_disabled_rules_file_stale(site, data_dir): return True except Exception as e: logger.error( "Error processing changelog for site %s: %s", site.docroot, e, ) return False def _consume_changelog( self, changelog_path: Path, site: WPSite ) -> list[dict]: """Parse a changelog file and delete it. The file is deleted regardless of whether parsing succeeds. """ try: return self.parser.parse_file(changelog_path) except (OSError, ValueError) as e: logger.error( "Failed to parse changelog for site %s: %s", site.docroot, e, ) return [] finally: try: changelog_path.unlink(missing_ok=True) except OSError as e: logger.error( "Failed to delete changelog for site %s: %s", site.docroot, e, ) async def _process_changelog_file( self, changelog_path: Path, site: WPSite, sink: MessageSink | None, ) -> bool: """Parse and apply actions from a changelog file. The file is always deleted after reading, even on parse errors. Actions older than the last sync timestamp are skipped to prevent stale changelog files (e.g. from backup restores) from undoing more recent changes. Returns: True if any DB changes occurred. """ actions = self._consume_changelog(changelog_path, site) if not actions: return False # str(site.uid): non-None sentinel (unused); avoids the root bypass if not await has_permission(WP_WAF_RULES_EDIT, str(site.uid)): logger.info( "WP WAF rule editing disabled by policy; dropping %d" " changelog action(s) for site %s", len(actions), site.docroot, ) return False last_sync_ts = self._get_last_sync_ts(site) changed = False for action in actions: try: timestamp = float(action.get("ts", 0)) if timestamp <= 0: raise ValueError( "Missing or invalid timestamp in changelog action" f" for rule {action.get('rule_id', '?')}" f" on site {site.docroot}" ) if last_sync_ts is not None and timestamp <= last_sync_ts: logger.info( "Skipping stale changelog action for rule %s" " on site %s (ts=%.0f <= sync_ts=%.0f)", action.get("rule_id", "?"), site.docroot, timestamp, last_sync_ts, ) continue if self._process_action(action, site, timestamp): changed = True await self._report_action(action, site, sink, timestamp) except ValueError as e: logger.warning("Skipping invalid changelog entry: %s", e) except Exception as e: logger.error( "Failed to process changelog action %s for site %s: %s", action, site.docroot, e, ) logger.info( "Processed changelog for site %s: %d action(s), changed=%s", site.docroot, len(actions), changed, ) return changed def _process_action( self, action: dict, site: WPSite, timestamp: float ) -> bool: """Apply a single changelog action to the database. Args: action: Parsed action dict with keys: action, rule_id, ts. site: The WordPress site the action belongs to. timestamp: Pre-resolved Unix timestamp for this action. Returns: True if the database state was modified. Raises: ValueError: If the action is missing required fields or has an unknown action type. """ action_type = action.get("action") rule_id = action.get("rule_id") if not action_type or not rule_id: raise ValueError( f"Missing action or rule_id in changelog entry: {action}" ) if action_type == ACTION_DISABLE: return self._apply_disable(rule_id, site, timestamp) elif action_type == ACTION_ENABLE: return self._apply_enable(rule_id, site) else: raise ValueError( f"Unknown changelog action '{action_type}'" f" for rule {rule_id} on site {site.docroot}" ) @staticmethod def _get_last_sync_ts(site: WPSite) -> float | None: """Get the last disabled-rules sync timestamp for a site. Returns None if the site has no DB record or no sync timestamp, meaning all actions should be processed. """ try: db_site = WordpressSite.get_by_id(site.docroot) return db_site.disabled_rules_sync_ts except WordpressSite.DoesNotExist: return None @staticmethod def _apply_disable(rule_id: str, site: WPSite, timestamp: float) -> bool: """Apply a disable action from the changelog. Returns: True if a new disable entry was created (not a no-op). """ count = WPDisabledRule.store( rule_id=rule_id, domains=[site.domain], source=WPDisabledRule.SOURCE_WORDPRESS, user_id=site.uid, timestamp=timestamp, ) return count > 0 def _apply_enable(self, rule_id: str, site: WPSite) -> bool: """Apply an enable action from the changelog. Returns: True if a disable entry was removed. """ count = WPDisabledRule.remove( rule_id=rule_id, domains=[site.domain], ) return count > 0 @staticmethod async def _report_action( action: dict, site: WPSite, sink: MessageSink | None, timestamp: float, ) -> None: """Send a rule change event to the correlation server. Must only be called for valid actions (after _process_action succeeds). """ if sink is None: return action_type = action["action"] rule_id = action["rule_id"] if action_type == ACTION_DISABLE: message_cls = MessageType.WPRuleDisabled elif action_type == ACTION_ENABLE: message_cls = MessageType.WPRuleEnabled else: return try: await sink.process_message( message_cls( plugin_id="wordpress", rule=rule_id, domains=[site.domain], timestamp=timestamp, user_id=site.uid, source=WPDisabledRule.SOURCE_WORDPRESS, ) ) except Exception as e: logger.error( "Failed to report changelog action for rule %s on site %s: %s", rule_id, site.docroot, e, ) @staticmethod def _is_disabled_rules_file_stale( site: WPSite, data_dir: Path, ) -> bool: """Check if disabled-rules.php was modified externally. Reads the embedded timestamp from the file and compares it against the stored sync timestamp in the database. If they differ (e.g. file restored from backup), returns True to trigger regeneration. """ disabled_rules_path = data_dir / DISABLED_RULES_FILENAME try: with open_nofollow(str(disabled_rules_path)) as fd: # dup: fdopen takes ownership, but open_nofollow also closes fd with os.fdopen(os.dup(fd), "r", encoding="utf-8") as f: content = f.read() except FileNotFoundError: return False except OSError as exc: if exc.errno != errno.ELOOP: logger.debug("Cannot open %s: %s", disabled_rules_path, exc) return False try: data = parse_php_with_embedded_json(content) file_ts = float(data.get("ts", 0)) except (OSError, ValueError) as e: logger.warning( "Cannot read disabled-rules.php for site %s: %s", site.docroot, e, ) return False try: db_site = WordpressSite.get_by_id(site.docroot) except WordpressSite.DoesNotExist: return False db_ts = db_site.disabled_rules_sync_ts return db_ts is None or abs(file_ts - db_ts) > 1.0 defence360agent/wordpress/cli.py0000644000000000000000000002533000000000000013722 0ustar import asyncio import logging import pwd import re from pathlib import Path from distutils.version import StrictVersion from defence360agent.utils import ( check_run, CheckRunError, async_lru_cache, ) from defence360agent.wordpress.constants import PLUGIN_PATH, PLUGIN_SLUG from defence360agent.wordpress.utils import ( build_command_for_user, get_php_binary_path, wp_wrapper, ) from defence360agent.sentry import log_message from defence360agent.model.wordpress import WPSite logger = logging.getLogger(__name__) def _validate_semver(version_str: str) -> bool: """Validate if a string is a valid semantic version.""" # Handle None and non-string inputs if not isinstance(version_str, str): return False # Trim the string and return False if empty trimmed_str = version_str.strip() if not trimmed_str: return False try: StrictVersion(trimmed_str) return True except ValueError: return False def _extract_version_from_output(output: str) -> str: """ Extract version from WP CLI output, trying both first and last parts. Args: output: The raw output from WP CLI Returns: The extracted version string or None if no valid version found """ if not output: return None # Split the output into parts parts = output.split() if not parts: return None # Try the first part if len(parts) > 0: first_part = parts[0].strip() if _validate_semver(first_part): return first_part # Try the last part if len(parts) > 1: last_part = parts[-1].strip() if _validate_semver(last_part): return last_part # If neither first nor last part is valid semver, log to sentry log_message( "Failed to extract valid semver version from WP CLI output. Output:" " '{output}'", format_args={"output": output}, level="warning", component="wordpress", fingerprint="wp-plugin-version-extraction-failed", ) # Return None when no valid semver is found return None async def _parse_version_from_plugin_file(site: WPSite) -> str: """ Parse the version of imunify-security plugin by reading the main plugin file. Args: site: WordPress site object containing docroot path Returns: str: Plugin version or None if not found or invalid """ content_dir = await get_content_dir(site) plugin_file = ( content_dir / "plugins" / "imunify-security" / "imunify-security.php" ) if not plugin_file.exists(): return None version_pattern = re.compile(r"\* Version:\s*([0-9.]+)") try: with open(plugin_file) as f: for line in f: match = version_pattern.search(line) if match: version = match.group(1) if _validate_semver(version): return version else: return None except Exception as e: logger.error( "Failed to read plugin file to determine version number %s: %s", plugin_file, e, ) return None return None async def plugin_install(site: WPSite): """Install the Imunify Security WordPress plugin on given WordPress site.""" username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "plugin", "install", str(PLUGIN_PATH), "--activate", "--force", ] command = build_command_for_user(username, args) logger.info(f"Installing wp plugin {command}") await check_run(command) async def plugin_update(site: WPSite): """ Update the Imunify Security WordPress plugin on given WordPress site. Currently, this is the same as install, but in the future it may differ. """ username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "plugin", "install", str(PLUGIN_PATH), "--activate", "--force", ] command = build_command_for_user(username, args) logger.info(f"Updating wp plugin {command}") await check_run(command) async def plugin_uninstall(site: WPSite): """Uninstall the imunify-security wp plugin from given wp site.""" username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "plugin", "uninstall", PLUGIN_SLUG, "--deactivate", ] command = build_command_for_user(username, args) logger.info(f"Uninstalling wp plugin {command}") await check_run(command) async def try_plugin_uninstall(site: WPSite) -> bool: """Attempt to uninstall the plugin, returning False on failure. Safe wrapper around plugin_uninstall for use in cleanup paths where failure should be logged but not raised. """ try: await plugin_uninstall(site) return True except Exception as error: logger.warning( "Failed to uninstall plugin from %s during cleanup: %s", site, error, ) return False async def _get_plugin_version(site: WPSite): """ Get the version of the imunify-security wp plugin installed on given WordPress site. Uses WP CLI to get the version. """ username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "plugin", "get", PLUGIN_SLUG, "--field=version", ] command = build_command_for_user(username, args) logger.info(f"Getting wp plugin version {command}") try: result = await check_run(command) output = result.decode("utf-8").strip() return _extract_version_from_output(output) except CheckRunError as e: logger.error( "Failed to get wp plugin version. Return code: %s", e.returncode, ) return None except UnicodeDecodeError as e: logger.error("Failed to decode wp plugin version output: %s", e) return None async def get_plugin_version(site: WPSite): """ Get the version of the imunify-security wp plugin installed on given WordPress site. First tries to parse the version from the plugin file, then falls back to WP CLI. """ # First try to parse version from plugin file try: version = await _parse_version_from_plugin_file(site) if version: return version except Exception as e: logger.warning("Failed to parse version from plugin file: %s", e) # Fall back to WP CLI if file parsing fails logger.info("Plugin version not found in file, trying WP CLI") return await _get_plugin_version(site) async def is_plugin_installed(site: WPSite): """Check if the imunify-security wp plugin is installed on given WordPress site.""" username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "plugin", "is-installed", PLUGIN_SLUG, ] command = build_command_for_user(username, args) logger.info(f"Checking if wp plugin is installed {command}") try: await check_run(command) except CheckRunError: # exit code other than 0 means plugin is not installed or there is an error return False # exit code 0 means plugin is installed return True async def is_wordpress_installed(site: WPSite): """Check if WordPress is installed and given site is accessible using WP CLI.""" username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "core", "is-installed", ] command = build_command_for_user(username, args) logger.info(f"Checking if WordPress is installed {command}") try: # exit code other than 0 means WordPress is not installed or there is an error await check_run(command) except CheckRunError: return False # exit code 0 means WordPress is installed return True async def _get_content_directory(site: WPSite): """ Get the content directory of the WordPress site using WP CLI. This should only be used if the default wp-content directory does not exist. """ username = pwd.getpwuid(site.uid).pw_name php_path = await get_php_binary_path(site, username) args = [ *wp_wrapper(php_path, site.docroot), "eval", "echo WP_CONTENT_DIR;", ] command = build_command_for_user(username, args) logger.info(f"Getting content directory {command}") try: result = await asyncio.wait_for(check_run(command), timeout=30) return result.decode("utf-8").strip() except asyncio.TimeoutError: logger.warning( "WP-CLI timed out getting content directory for %s", site.docroot ) return None except CheckRunError as e: logger.error( "Failed to get content directory. Return code: %s", e.returncode, ) return None except UnicodeDecodeError as e: logger.error("Failed to decode content directory output: %s", e) return None @async_lru_cache(maxsize=100) async def get_content_dir(site: WPSite): """ Get the WordPress content directory for the given WordPress site. This function first checks if the default wp-content directory exists at the site's docroot. If the default path doesn't exist or isn't a directory, it attempts to get the actual content directory using WordPress CLI's WP_CONTENT_DIR constant. Returns: Path: The WordPress content directory path """ content_dir = Path(site.docroot) / "wp-content" # First check if content_dir exists and is a folder if not content_dir.exists() or not content_dir.is_dir(): # If not, try to get the content directory using WP CLI wp_content_dir = await _get_content_directory(site) if wp_content_dir: content_dir = Path(wp_content_dir) return content_dir def clear_get_content_dir_cache(): """Clear the async LRU cache for get_content_dir.""" get_content_dir.cache_clear() async def get_data_dir(site: WPSite): """ Get the Imunify Security data directory for the given WordPress site. """ content_dir = await get_content_dir(site) if not content_dir: content_dir = Path(site.docroot) / "wp-content" return Path(content_dir) / "imunify-security" defence360agent/wordpress/constants.py0000644000000000000000000000053200000000000015164 0ustar """Constants for WordPress module.""" from pathlib import Path PLUGIN_PATH = Path("/usr/share/imunify360/wp-plugins/imunify-security.zip") PLUGIN_SLUG = "imunify-security" PLUGIN_VERSION_FILE = Path( "/usr/share/imunify360/wp-plugins/imunify-security.version" ) WP_CLI_WRAPPER_PATH = Path("/usr/share/imunify360/wp-plugins/wp-cli-wrapper") defence360agent/wordpress/exception.py0000644000000000000000000000013600000000000015146 0ustar class PHPError(Exception): def __init__(self, message): super().__init__(message) defence360agent/wordpress/incident_collector.py0000644000000000000000000005145400000000000017024 0ustar """Collector for WordPress CVE protection incidents.""" import logging import os import pwd import stat as stat_module import time import re from pathlib import Path from collections import defaultdict from defence360agent.model.wordpress import WPSite from defence360agent.wordpress.cli import get_data_dir from defence360agent.wordpress.incident_parser import IncidentFileParser from defence360agent.model.wordpress_incident import ( aggregate_incident_dicts, bulk_create_wordpress_incidents, build_incident_dict, country_reader, ) logger = logging.getLogger(__name__) #: Marks a file taken aside for processing. The plugin keeps appending to a #: freshly created file under the original name, so nothing written during the #: batch is lost, and a file left behind is retried on the next cycle. The #: .php extension stays last: a webserver that only hands *.php to the #: interpreter would serve any other extension as readable text. PROCESSING_SUFFIX = ".processing.php" #: A batch this old has failed every cycle since it was set aside. Retiring it #: stops the retry from repeating forever and unblocks the same-hour file it #: would otherwise keep out of collection. QUARANTINE_AFTER_SECONDS = 15 * 60 #: Terminal names for a batch the collector must not pick up again. Neither #: is matched by the pattern, and both keep .php last for the same reason #: PROCESSING_SUFFIX does. A stored batch says so: its incidents are safe. FAILED_SUFFIX = ".failed.php" STORED_SUFFIX = ".stored.php" class IncidentRateLimiter: """ Rate limiter to prevent DoS attacks via incident flooding. Implements per-rule-per-IP rate limiting as per spec: - Maximum 100 incidents for each rule from the same IP within 15 minutes Memory-optimized implementation with bounded entry count using LRU eviction. """ def __init__( self, max_incidents_per_rule_per_ip: int = 100, time_window_seconds: int = 900, # 15 minutes max_unique_entries: int = 10000, # Limit total unique (rule_id, IP) combinations ): """ Initialize the rate limiter. Args: max_incidents_per_rule_per_ip: Max incidents per rule per IP (default: 100) time_window_seconds: Time window in seconds (default: 900 = 15 minutes) max_unique_entries: Max unique (rule_id, IP) combinations to track (default: 10000) """ self.max_per_rule_per_ip = max_incidents_per_rule_per_ip self.time_window = time_window_seconds self.max_unique_entries = max_unique_entries # Track incident timestamps: {(rule_id, ip): [timestamp1, timestamp2, ...]} self.incident_times = defaultdict(list) self.cleanup_interval = 60 # Clean up old records every minute self.last_cleanup = time.time() def _cleanup_old_records(self): """Remove records older than the time window and enforce max entries limit.""" now = time.time() cutoff = now - self.time_window # Clean expired timestamps from all entries keys_to_delete = [] for key, timestamps in self.incident_times.items(): # Filter out timestamps older than the window recent = [ts for ts in timestamps if ts > cutoff] if recent: self.incident_times[key] = recent else: keys_to_delete.append(key) for key in keys_to_delete: del self.incident_times[key] # Enforce max unique entries limit using LRU eviction if len(self.incident_times) > self.max_unique_entries: # Find oldest entries (those with oldest timestamp) entries_by_age = sorted( self.incident_times.items(), key=lambda x: x[1][0] if x[1] else 0, ) # Remove oldest 10% of entries to avoid frequent evictions num_to_remove = max( 1, len(self.incident_times) - int(self.max_unique_entries * 0.9), ) for key, _ in entries_by_age[:num_to_remove]: del self.incident_times[key] logger.warning( "Rate limiter exceeded max entries (%d), removed %d oldest" " entries", self.max_unique_entries, num_to_remove, ) self.last_cleanup = now def check_rate_limit( self, rule_id: str, attacker_ip: str, pending: int = 0 ) -> tuple[bool, str]: """ Check if adding an incident would exceed rate limits. Args: rule_id: Rule identifier attacker_ip: IP address of the attacker pending: Incidents already accepted in the current batch but not recorded yet, so one file cannot exceed the limit on its own Returns: Tuple of (allowed: bool, reason: str) """ # Periodic cleanup if time.time() - self.last_cleanup > self.cleanup_interval: self._cleanup_old_records() now = time.time() cutoff = now - self.time_window key = (rule_id, attacker_ip) # Lazy cleanup: remove expired entries on access if key in self.incident_times: timestamps = self.incident_times[key] # Filter out old timestamps recent = [ts for ts in timestamps if ts > cutoff] if recent: self.incident_times[key] = recent recent_count = len(recent) else: # All timestamps expired, remove entry del self.incident_times[key] recent_count = 0 else: recent_count = 0 # Check if limit exceeded recent_count += pending if recent_count >= self.max_per_rule_per_ip: window_minutes = self.time_window // 60 return ( False, ( f"Rate limit exceeded for rule {rule_id} from IP" f" {attacker_ip}:" f" {recent_count}/{self.max_per_rule_per_ip} within" f" {window_minutes} minutes" ), ) return True, "OK" def record_incident(self, rule_id: str, attacker_ip: str): """ Record that an incident was added. Args: rule_id: Rule identifier attacker_ip: IP address """ now = time.time() key = (rule_id, attacker_ip) # Create list if it doesn't exist, or append to existing if key not in self.incident_times: self.incident_times[key] = [now] else: # Limit list size to prevent unbounded growth timestamps = self.incident_times[key] if len(timestamps) >= self.max_per_rule_per_ip: # Remove oldest timestamp when at limit timestamps.pop(0) timestamps.append(now) class IncidentCollector: """ Collect and persist WordPress incidents from plugin incident files. """ def __init__(self, rate_limiter: IncidentRateLimiter | None = None): """ Initialize the incident collector. Args: rate_limiter: Optional rate limiter (creates default if not provided) """ self.rate_limiter = rate_limiter or IncidentRateLimiter() self.parser = IncidentFileParser() #: Batches this process read and failed to clear. The agent idles out #: after minutes of quiet, so age alone would retire one that was #: never retried. self._failed: set[str] = set() async def collect_incidents_for_site( self, site: WPSite, delete_after_processing: bool = True, ) -> list: """ Collect incidents from a single WordPress site. Args: site: WordPress site to collect incidents from ruleset_version: Version of the ruleset being used delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects """ collected_incidents = [] try: data_dir = await get_data_dir(site) logger.debug("Data directory for site %s: %s", site, data_dir) if not data_dir.exists(): logger.debug("Data directory does not exist for site %s", site) return [] incident_files = self._get_incident_files(data_dir) logger.debug( "Incident files for site %s: %s", site, incident_files ) if not incident_files: logger.debug("No incident files found for site %s", site) return [] logger.debug( "Found %d incident file(s) for site %s", len(incident_files), site, ) username = self._get_site_username(site) for incident_file in incident_files: file_incidents = await self._process_file( incident_file, site, username, delete_after_processing, ) collected_incidents.extend(file_incidents) except Exception as e: logger.error( "Error collecting incidents for site %s: %s", site, e, ) logger.info( "Collected %d incident(s) for site %s", len(collected_incidents), site, ) return collected_incidents async def collect_incidents_for_sites( self, sites: list[WPSite], delete_after_processing: bool = True, ) -> list: """ Collect incidents from multiple WordPress sites. Args: sites: List of WordPress sites delete_after_processing: Whether to delete incident files after processing Returns: List of collected Incident objects """ all_collected_incidents = [] for site in sites: site_incidents = await self.collect_incidents_for_site( site, delete_after_processing, ) all_collected_incidents.extend(site_incidents) if all_collected_incidents: logger.info( "Collected %d WordPress incident(s) from %d site(s)", len(all_collected_incidents), len(sites), ) return all_collected_incidents @classmethod def _get_incident_files(cls, data_dir: Path) -> list[Path]: """ Get all incident files in the incidents directory. Args: data_dir: Path to the imunify-security data directory Returns: List of incident file paths """ incidents_dir = data_dir / "incidents" logger.debug( "Incidents directory for site %s: %s", data_dir, incidents_dir ) if not incidents_dir.exists() or not incidents_dir.is_dir(): logger.debug( "Incidents directory does not exist for site %s", data_dir ) return [] # Use lstat (not Path.is_file) to identify regular files without following symlinks. incident_files = [] for f in incidents_dir.iterdir(): try: st = os.lstat(f) except OSError: continue if stat_module.S_ISREG(st.st_mode) and cls._is_incident_file(f): incident_files.append(f) logger.debug( "Incident files for site %s: %s", data_dir, incident_files ) return incident_files # Pattern for incident files: yyyy-mm-dd-hh.php, optionally taken aside _FILE_PATTERN = re.compile( r"^\d{4}-\d{2}-\d{2}-\d{2}(?:\.processing)?\.php$" ) @classmethod def _is_incident_file(cls, file_path: Path) -> bool: """ Check if a file is an incident file based on naming pattern. Args: file_path: Path to the file to check Returns: True if file matches pattern yyyy-mm-dd-hh.php, with or without the suffix marking a batch left behind by an earlier cycle """ return bool(cls._FILE_PATTERN.match(file_path.name)) async def _process_file( self, incident_file, site: WPSite, username: str | None, delete_after_processing: bool, ) -> list: try: if delete_after_processing: incident_file = self._take_aside(incident_file) if incident_file is None: return [] incidents = self.parser.parse_file(incident_file) if incidents is None: self._failed.add(str(incident_file)) return [] if not incidents: logger.warning( "No valid incidents in file %s", incident_file.name, ) if delete_after_processing: self._discard(incident_file) return [] logger.debug( "Parsed %d incident(s) from %s for site %s", len(incidents), incident_file.name, site, ) collected_incidents = self._process_file_incidents( incidents, site, username, incident_file.name, ) if delete_after_processing: self._discard(incident_file) return collected_incidents except Exception as e: if delete_after_processing: self._failed.add(str(incident_file)) logger.error( "Error processing incident file %s for site %s: %s", incident_file.name, site, e, ) return [] def _take_aside(self, incident_file: Path) -> Path | None: """Move the file out of the plugin's way before reading it. Returns None when an earlier batch is still pending under the aside name; that batch is processed in its own turn and the fresh file waits for the next cycle rather than overwriting it. """ if incident_file.name.endswith(PROCESSING_SUFFIX): if self._quarantine(incident_file): return None return incident_file aside = incident_file.with_name( incident_file.name[: -len(".php")] + PROCESSING_SUFFIX ) if aside.exists() and self._pending(aside): if not self._quarantine(aside): return None incident_file.rename(aside) # rename keeps the plugin's mtime, so stamp the file to date the # attempt rather than the last write to it. os.utime(aside, None, follow_symlinks=False) return aside @staticmethod def _pending(aside: Path) -> bool: """Whether an aside still holds a batch waiting to be stored. Anything the site put there that is not a regular file is not one, and the rename replaces it. """ try: st = os.lstat(aside) except OSError: return False return stat_module.S_ISREG(st.st_mode) and st.st_size > 0 def _retire(self, path: Path, suffix: str) -> Path | None: """Give a batch a name the collector will not pick up again. Renaming touches the name, never what it points at, so it stays safe in a directory the site owns. """ stem = path.name for known in (PROCESSING_SUFFIX, ".php"): if stem.endswith(known): stem = stem[: -len(known)] break retired = path.with_name(stem + suffix) try: path.rename(retired) except OSError as e: logger.error("Failed to retire %s: %s", path.name, e) return None self._failed.discard(str(path)) return retired def _quarantine(self, aside: Path) -> bool: """Retire an aside this process has read and still failed to clear.""" if str(aside) not in self._failed: return False try: age = time.time() - os.lstat(aside).st_mtime except OSError: return True if age < QUARANTINE_AFTER_SECONDS: return False retired = self._retire(aside, FAILED_SUFFIX) if retired is None: return False logger.error( "Gave up on %s after %d seconds, kept it as %s", aside.name, age, retired.name, ) return True def _discard(self, incident_file: Path) -> bool: """Delete a stored batch, reporting whether it is gone. Emptying one we cannot delete would mean writing through a path the site owns, so it is retired under a name we never collect instead. """ try: incident_file.unlink(missing_ok=True) self._failed.discard(str(incident_file)) return True except OSError as e: logger.error("Failed to delete %s: %s", incident_file.name, e) # Its incidents are stored: reading the file again would add to their # counts, so retire it now rather than leaving it to be picked up. retired = self._retire(incident_file, STORED_SUFFIX) if retired is None: self._failed.add(str(incident_file)) else: logger.warning( "Could not delete %s, kept the stored batch as %s", incident_file.name, retired.name, ) return False def _get_site_username(self, site: WPSite) -> str | None: try: user_info = pwd.getpwuid(site.uid) return user_info.pw_name except Exception as e: logger.error( "Failed to get username for uid=%d, site %s: %s", site.uid, site, e, ) return None def _process_file_incidents( self, incidents: list[dict], site: WPSite, username: str | None, incident_file_name: str, ) -> list: incidents_to_insert = [] accepted: defaultdict = defaultdict(int) dropped_count = 0 # Prepare all incidents for bulk insertion with country_reader() as geo_reader: for incident in incidents: rule_id = incident.get("rule_id", "unknown") attacker_ip = incident.get("REMOTE_ADDR") or incident.get( "attacker_ip", "unknown" ) allowed, reason = self.rate_limiter.check_rate_limit( rule_id, attacker_ip, pending=accepted[(rule_id, attacker_ip)], ) if not allowed: logger.warning( "Rate limit exceeded for site %s: %s", site, reason, ) dropped_count += 1 continue # Prepare incident data for bulk insert site_info = { "domain": site.domain, "site_path": site.docroot, "username": username, "user_id": site.uid, } incident_data = build_incident_dict( incident, site_info, geo_reader=geo_reader ) incidents_to_insert.append(incident_data) accepted[(rule_id, attacker_ip)] += 1 if not incidents_to_insert: logger.info( "Processed file %s: 0 stored, 0 aggregated, %d dropped", incident_file_name, dropped_count, ) return [] aggregated = aggregate_incident_dicts(incidents_to_insert) try: bulk_create_wordpress_incidents(aggregated) except Exception: logger.error( "Failed to store %d incident(s) from %s, keeping the file" " for the next cycle", len(incidents_to_insert), incident_file_name, exc_info=True, ) raise # Only a stored incident spends rate-limit budget; a kept file is # retried next cycle and must not be throttled away unstored. for (rule_id, attacker_ip), count in accepted.items(): for _ in range(count): self.rate_limiter.record_incident(rule_id, attacker_ip) logger.info( "Processed file %s: %d stored, %d aggregated, %d dropped", incident_file_name, len(aggregated), len(incidents_to_insert) - len(aggregated), dropped_count, ) return aggregated defence360agent/wordpress/incident_parser.py0000644000000000000000000001172400000000000016326 0ustar """Parser for WordPress plugin incident files.""" import base64 import json import logging import math import os from pathlib import Path from defence360agent.utils.fd_ops import open_nofollow logger = logging.getLogger(__name__) class IncidentFileParser: """ Parse incident files written by the WordPress plugin. These files have format: list[dict] | None: """Parse an incident file, or None when it could not be read. A file that could not be read is not an empty one: the caller keeps it for the next cycle instead of discarding a batch it never saw. The file format is: - First line: dict | None: """ Process a single line from an incident file. Args: line: The line content (already stripped) line_num: Line number for logging file_path: Path to the file being processed Returns: Parsed incident dictionary or None if line should be skipped """ # Skip empty lines if not line: return None if line.startswith(" dict | None: """ Decode base64-encoded JSON data from an incident line. Args: encoded_data: Base64-encoded JSON string line_num: Line number for logging file_path: Path to the file being processed Returns: Parsed incident dictionary or None if decoding/parsing fails """ try: decoded_bytes = base64.b64decode(encoded_data) decoded_str = decoded_bytes.decode("utf-8") incident = json.loads(decoded_str) if not isinstance(incident, dict): logger.warning( "Line %d in %s is not a JSON object: %s", line_num, file_path.name, decoded_str[:100], ) return None if not cls._has_valid_timestamp(incident): logger.warning( "Line %d in %s has no usable ts: %r", line_num, file_path.name, incident.get("ts"), ) return None return incident except (Exception, json.JSONDecodeError) as e: logger.error( "Failed to decode base64 on line %d in %s: %s", line_num, file_path.name, e, ) return None @staticmethod def _has_valid_timestamp(incident: dict) -> bool: """The timestamp decides the aggregation window, so it must be usable. json.loads accepts Infinity and NaN, which survive a bare > 0 check and blow up when the window is computed. """ try: ts = float(incident["ts"]) except (KeyError, TypeError, ValueError): return False return math.isfinite(ts) and ts > 0 defence360agent/wordpress/incident_sender.py0000644000000000000000000002513100000000000016307 0ustar """Send WordPress incidents to the correlation server.""" import hashlib import itertools import json import logging from datetime import datetime from functools import partial from time import monotonic from types import MappingProxyType from typing import Any, Mapping from defence360agent.contracts.messages import SensorWordpressIncidentList from defence360agent.contracts.plugins import MessageSink from defence360agent.internals import delivery_ack from defence360agent.model.wordpress_incident import ( get_unsent_wordpress_incidents, settle_wordpress_incidents_reported, ) logger = logging.getLogger(__name__) class IncidentSender: """ Send WordPress incidents to the correlation server. WordPress incidents are already in the Incident table (visible to UI). This class sends them to correlation via Reportable messages, which are handled by the SendToServer/SendToServerNATS/SendToServerFGW plugins. Those plugins queue a message rather than deliver it, and a send round can lose its batch or be force-cancelled mid-publish while the agent shuts down. Each incident therefore keeps a count of the occurrences the transport has not acknowledged, and every collection cycle sends whatever is still outstanding. """ # rows one cycle takes on, so a backlog is paged instead of read whole; # the byte budget, not this, is what bounds a single message MAX_INCIDENTS_PER_CYCLE = 1000 # how long to wait for an acknowledgement before assuming the round was # lost; also paces retries, since a batch nobody acknowledges is re-sent # at most once per timeout ACK_TIMEOUT = 300 def __init__(self) -> None: # message_id -> ({incident id: occurrences it reported}, deadline) self._inflight: dict[str, tuple[dict[int, int], float]] = {} def _prepare_incident_for_correlation( self, incident: dict ) -> dict[str, Any]: """ Prepare an incident for sending to the correlation server. WordPress incidents use extra_info JSON field to store plugin-specific data. Args: incident: WordpressIncident dictionary (with extra_info populated) Returns: Dictionary formatted for correlation server """ logger.info("Preparing incident for correlation: %s", incident) # JSONField automatically deserializes to dict, fallback to empty dict if None extra: dict = incident.get("extra_info") or {} # Convert timestamp to int and format date timestamp_value: float = float(incident.get("timestamp") or 0) timestamp = int(timestamp_value) dt = ( datetime.fromtimestamp(timestamp_value).strftime("%Y-%m-%d") if timestamp_value else "" ) return { "timestamp": timestamp, "dt": dt, "plugin_id": incident.get("plugin"), "rule": incident.get("rule") or "unknown", "name": incident.get("name"), "message": incident.get("description"), "severity": incident.get("severity"), "attackers_ip": incident.get("abuser") or "", "domain": incident.get("domain") or "", # the occurrences this message reports, not the row's running # total: re-reporting occurrences correlation already counted # inflates the heuristics that consume this field "retries": incident.get("unsent_retries") or 1, "uri": extra.get("request_uri") or "", "user_agent": extra.get("http_user_agent") or "", "http_method": extra.get("request_method") or "", "user_logged_in": extra.get("user_logged_in") == "true" if extra.get("user_logged_in") else None, "file_path": extra.get("site_path") or "", "user": extra.get("username") or "", "tag": self._build_tags(extra), # Include WordPress-specific fields "target": extra.get("target") or "", "slug": extra.get("slug") or "", "version": extra.get("version") or "", "mode": extra.get("mode") or "", "details": extra, } def _build_tags(self, extra: dict) -> list[str]: tags = ["wordpress", "cve"] if extra.get("cve"): tags.append(extra["cve"]) if extra.get("target"): tags.append(f"target_{extra['target']}") if extra.get("mode"): tags.append(f"mode_{extra['mode']}") return tags async def send_pending_incidents(self, sink: MessageSink | None) -> int: """Send every incident the server has not acknowledged. Runs once per collection cycle, after the freshly collected incidents were stored, so one pass covers both the new rows and the ones whose earlier message never left the agent. """ if sink is None: logger.warning("No sink provided, skipping incident sending") return 0 self._expire_inflight() incidents = get_unsent_wordpress_incidents( limit=self.MAX_INCIDENTS_PER_CYCLE, exclude_ids=self._inflight_ids(), ) return await self.send_incidents(sink, incidents) async def send_incidents( self, sink: MessageSink | None, incidents: list[dict] ) -> int: """ Send WordPress incidents to the correlation server. Since incidents are already in the WordpressIncident table (visible to UI), we just need to send them to correlation. Args: incidents: List of incidents to send Returns: Number of incidents sent """ if sink is None: logger.warning("No sink provided, skipping incident sending") return 0 if len(incidents) == 0: logger.debug("No incidents to send, skipping") return 0 logger.info( "Sending %d incidents to correlation server", len(incidents) ) correlation_batch = [ self._prepare_incident_for_correlation(incident) for incident in incidents ] pending = iter( [ (incident.get("id"), incident.get("unsent_retries") or 1) for incident in incidents ] ) for batch in SensorWordpressIncidentList.batched(correlation_batch): await self._send_batch( sink, batch, { incident_id: occurrences for incident_id, occurrences in itertools.islice( pending, len(batch) ) if incident_id is not None }, ) return len(correlation_batch) async def _send_batch( self, sink: MessageSink, correlation_batch: list[dict], reported: Mapping[int, int] = MappingProxyType({}), ): """Send a batch of incidents to correlation server. Uses SensorIncidentList Reportable message which is sent to correlation via the SendToServer/SendToServerNATS/SendToServerFGW plugins. """ logger.info( "Sending batch of %d incidents to correlation server", len(correlation_batch), ) logger.info( "Correlation batch json: %s", json.dumps(correlation_batch, indent=2), ) message = SensorWordpressIncidentList(correlation_batch) # Pin the id the send path would otherwise generate itself, so its # acknowledgement can be tied back to these rows. Deriving it from the # rows also makes a re-send carry the id of the message it repeats, # which lets the transport's de-duplication window collapse the two. message_id = hashlib.sha1( json.dumps( [sorted(reported.items()), correlation_batch], sort_keys=True ).encode() ).hexdigest() message["message_id"] = message_id self._watch(message_id, reported) try: await sink.process_message(message) logger.info( "Queued %d wordpress incident(s) for correlation server" " (message %s)", len(correlation_batch), message_id, ) except Exception as e: # the message never made it into the queue, so stop waiting for # an acknowledgement and let the next cycle send it again self._unwatch(message_id) logger.error( "Failed to queue incident batch: %s", e, ) raise def _watch(self, message_id: str, reported: Mapping[int, int]) -> None: if not reported: return reported = dict(reported) self._inflight[message_id] = ( reported, monotonic() + self.ACK_TIMEOUT, ) delivery_ack.registry.watch( message_id, partial(self._on_delivered, message_id, reported), ) def _unwatch(self, message_id: str) -> None: self._inflight.pop(message_id, None) delivery_ack.registry.unwatch(message_id) def _on_delivered(self, message_id: str, reported: dict[int, int]) -> None: # written here rather than buffered for the next cycle because a # send round usually lands during shutdown, and a count kept in # memory until then would not survive the restart self._inflight.pop(message_id, None) settled = settle_wordpress_incidents_reported(reported) logger.info( "Discounted %d wordpress incident(s) delivered to correlation", settled, ) def _expire_inflight(self) -> None: """Give up on batches the transport never acknowledged, so their incidents become eligible to be sent again.""" now = monotonic() expired = [ message_id for message_id, (_, deadline) in self._inflight.items() if deadline <= now ] for message_id in expired: reported, _ = self._inflight.pop(message_id) delivery_ack.registry.unwatch(message_id) logger.warning( "No delivery confirmation for %d wordpress incident(s)" " (message %s) in %ds, sending them again", len(reported), message_id, self.ACK_TIMEOUT, ) def _inflight_ids(self) -> set[int]: return { incident_id for reported, _ in self._inflight.values() for incident_id in reported } defence360agent/wordpress/plugin.py0000644000000000000000000022411100000000000014447 0ustar import asyncio import errno import logging import os import pwd import time from collections import defaultdict from collections.abc import Awaitable, Callable from distutils.version import LooseVersion from functools import cache from pathlib import Path from defence360agent.api import inactivity from defence360agent.contracts.config import ( MalwareScanScheduleInterval as Interval, SystemConfig, ANTIVIRUS_MODE, UserType, choose_value_from_config, ) from defence360agent.files import Index, WP_RULES from defence360agent.sentry import log_message from defence360agent.utils import importer from defence360agent.utils.fd_ops import ( open_dir_no_symlinks, open_nofollow, rmtree_fd, safe_dir, ) from defence360agent.contracts.config import Wordpress from defence360agent.subsys.panels import hosting_panel from defence360agent.wordpress.wp_rules import ( get_wp_rules_data, get_wp_ruleset_version, ) from defence360agent.model.wordpress import WordpressSite, WPSite from defence360agent.model.wp_disabled_rule import WPDisabledRule from defence360agent.wordpress import cli, telemetry from defence360agent.wordpress.constants import PLUGIN_VERSION_FILE from defence360agent.wordpress.utils import ( _validate_preset, calculate_next_scan_timestamp, clear_get_cagefs_enabled_users_cache, ensure_site_data_directory, format_php_with_embedded_json, get_imunify_package_versions, get_last_scan, get_malware_history, prepare_plugin_config, prepare_scan_data, write_plugin_data_file_atomically, ) from defence360agent.wordpress.site_repository import ( clear_manually_deleted_flag, delete_site, get_installed_sites_by_domains, get_outdated_sites, get_sites_for_user, get_sites_to_adopt, get_sites_to_install, get_sites_to_mark_as_manually_deleted, get_installed_sites, insert_installed_sites, mark_site_as_manually_deleted, update_site_identity, update_site_version, ) from defence360agent.wordpress.proxy_auth import setup_site_authentication logger = logging.getLogger(__name__) @cache def _get_user_schedule_config_imav(): return importer.get( module="imav.malwarelib.plugins.schedule_watcher", name="get_user_schedule_config", default=None, ) # Fallback when WORDPRESS keys are missing from config (old schema). # True keeps WAF on for old schemas — no behavior change on upgrade. _LEGACY_WAF_FALLBACK = True _apply_waf_default_lock = asyncio.Lock() _apply_waf_default_pending = False # Default when ai_bot_protection is missing from config (old schema on # a host where sibling packages haven't shipped the field yet). False # because the feature is opt-in — if we can't determine admin intent, # stay off rather than silently activate request-blocking logic. _AI_BOT_PROTECTION_DEFAULT = False _AI_BOT_PROTECTION_PRESET_DEFAULT = "balanced" def _get_global_waf_enabled() -> bool: try: return bool(Wordpress.WAF_ENABLED) except KeyError: return _LEGACY_WAF_FALLBACK def _get_waf_default() -> bool: try: return bool(Wordpress.WAF_DEFAULT) except KeyError: return _LEGACY_WAF_FALLBACK def _get_security_plugin_enabled() -> bool: # KeyError -> False (feature off), matching the schema default. Unlike the # WAF keys there is no legacy "on" fallback: an absent key means the # feature simply isn't present on this schema, and a missing key must not # crash a read-only caller during agent/imunify-antivirus version skew. try: return bool(Wordpress.SECURITY_PLUGIN_ENABLED) except KeyError: return False def _get_global_ai_bot_protection() -> bool: """Read WORDPRESS.ai_bot_protection from config, defaulting to False. Returns _AI_BOT_PROTECTION_DEFAULT when the config key is missing — e.g. the ai_bot_protection field hasn't rolled out to this install's imunify360 yet, or a sibling package is still on an older schema. Keeps the feature off in all ambiguous cases. """ try: return bool(Wordpress.AI_BOT_PROTECTION) except KeyError: return _AI_BOT_PROTECTION_DEFAULT def _get_global_ai_bot_protection_preset() -> str: """Read WORDPRESS.ai_bot_protection_preset from config, defaulting to "balanced". Two layers of safety: KeyError on a missing key (older schema, agent upgrade in progress) and _validate_preset() on the value itself (hand-edited override file, future preset rolled in via a sibling package this version doesn't recognise). Both fall back to the same canonical default so all layers — schema, agent, plugin — agree. """ try: raw = Wordpress.AI_BOT_PROTECTION_PRESET except KeyError: return _AI_BOT_PROTECTION_PRESET_DEFAULT return _validate_preset(raw) WAF_SOURCE_DEFAULT = "default" WAF_SOURCE_OVERRIDE = "override" WAF_SOURCE_KILL_SWITCH = "global kill switch" def waf_global_snapshot() -> tuple[bool, bool, bool]: """Read the three server-wide WAF flags in one call. Returns (security_plugin_enabled, global_waf_enabled, waf_default), each guarded against a missing config key (schema version skew during an agent/imunify-antivirus upgrade) the same way the individual accessors are. """ return ( _get_security_plugin_enabled(), _get_global_waf_enabled(), _get_waf_default(), ) def waf_status_and_source_for_user_sync(username: str) -> tuple[bool, str]: if not _get_global_waf_enabled(): return False, WAF_SOURCE_KILL_SWITCH try: value, source = choose_value_from_config( "WORDPRESS", "waf_enabled", username=username ) except KeyError: return _get_waf_default(), WAF_SOURCE_DEFAULT if source == UserType.ROOT: return _get_waf_default(), WAF_SOURCE_DEFAULT return bool(value), WAF_SOURCE_OVERRIDE def _is_waf_enabled_for_user_sync(username: str) -> bool: enabled, _ = waf_status_and_source_for_user_sync(username) return enabled async def is_waf_enabled_for_user(username: str) -> bool: """Async wrapper — runs config file I/O in executor.""" loop = asyncio.get_running_loop() return await loop.run_in_executor( None, _is_waf_enabled_for_user_sync, username ) def _user_has_explicit_waf_override_sync(username: str) -> bool: try: _, source = choose_value_from_config( "WORDPRESS", "waf_enabled", username=username ) except KeyError: return False return source != UserType.ROOT COMPONENTS_DB_PATH = Path( "/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3" ) def _get_user_schedule_config(username: str, admin_config: SystemConfig): """ Get user-specific schedule configuration with lazy import fallback. Returns default values if imav.malwarelib is not available. """ get_user_schedule_config = _get_user_schedule_config_imav() if get_user_schedule_config is None: logger.debug( "imav.malwarelib not available, returning default schedule config" ) return Interval.NONE, 0, 1, 0 return get_user_schedule_config(username, admin_config) def get_updated_wp_rules_data(index: Index) -> dict | None: """ Retrieve WordPress rules with ANTIVIRUS_MODE handling and global disable filtering. In ANTIVIRUS_MODE, all rules are set to monitoring mode ("pass"). Globally disabled rules are filtered out entirely — they should not appear in rules.php. Domain-specific disables are handled separately via disabled-rules.php. Args: index: The Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data with mode adjusted for ANTIVIRUS_MODE and globally disabled rules removed, or None if rules cannot be loaded. """ rules_data = get_wp_rules_data(index) if rules_data is None: return None if ANTIVIRUS_MODE: # all rules will be in monitoring mode only for AV and AV+ for cve, params in rules_data.items(): params["mode"] = "pass" # Filter out globally disabled rules — these are excluded from rules.php globally_disabled = set(WPDisabledRule.get_global_disabled()) if globally_disabled: rules_data = { cve: params for cve, params in rules_data.items() if cve not in globally_disabled } return rules_data def clear_caches(): """Clear all WordPress-related caches.""" clear_get_cagefs_enabled_users_cache() cli.clear_get_content_dir_cache() def site_search(items: dict, user_info: pwd.struct_passwd, matcher) -> dict: # Get all WordPress sites for the user (the main site is always last) user_sites = get_sites_for_user(user_info) result = {path: [] for path in user_sites} for item in items: # Find all matching sites for this item matching_sites = [path for path in user_sites if matcher(item, path)] if matching_sites: # Find the most specific (longest) matching path most_specific_site = max(matching_sites, key=len) result[most_specific_site].append(item) return result async def _get_scan_data_for_user( sink, user_info: pwd.struct_passwd, admin_config: SystemConfig ): # Get the last scan data last_scan = await get_last_scan(sink, user_info.pw_name) # Extract the last scan date last_scan_time = last_scan.get("scan_date", None) # Get user-specific schedule configuration interval, hour, day_of_month, day_of_week = _get_user_schedule_config( user_info.pw_name, admin_config ) next_scan_time = None if interval != Interval.NONE: next_scan_time = calculate_next_scan_timestamp( interval, hour, day_of_month, day_of_week ) # Get the malware history for the user malware_history = get_malware_history(user_info.pw_name) # Split malware history by site. This part relies on the main site being the last one in the list. # Without this all malware could be attributed to the main site. malware_by_site = site_search( malware_history, user_info, lambda item, path: item["resource_type"] == "file" and item["file"].startswith(path), ) return last_scan_time, next_scan_time, malware_by_site async def _send_telemetry_task(coro, semaphore: asyncio.Semaphore): async with semaphore: try: await coro except Exception as e: logger.error(f"Telemetry task failed: {e}") async def process_telemetry_tasks(coroutines: list, concurrency=10): """ Process a list of telemetry coroutines with a concurrency limit.s """ if not coroutines: return semaphore = asyncio.Semaphore(concurrency) tasks = [ asyncio.create_task(_send_telemetry_task(coro, semaphore)) for coro in coroutines ] try: await asyncio.gather(*tasks) except Exception as e: logger.error(f"Some telemetry tasks failed: {e}") async def load_wp_rules_php(): """ Load WordPress rules from the index and format them as PHP. Returns: str or None: PHP-formatted rules data, or None if rules could not be loaded. """ try: wp_rules_index = Index(WP_RULES, integrity_check=False) await wp_rules_index.update() wp_rules_data = get_updated_wp_rules_data(wp_rules_index) except Exception as e: logger.warning( "Failed to load wp-rules index: %s, skipping rules installation", e, ) return None if not wp_rules_data: logger.warning( "valid WordPress rules not found, skipping rules installation" ) return None # Get version and create ruleset dict with version and rules wp_rules_version = get_wp_ruleset_version(wp_rules_index) ruleset_dict = { "version": wp_rules_version, "rules": wp_rules_data, } return format_php_with_embedded_json(ruleset_dict) async def install_everywhere(sink): """Install the imunify-security plugin for all sites where it is not installed.""" sites = get_sites_to_install() installer = WordPressSiteInstaller(sink, sites) return await installer.run() async def adopt_found_sites(sink): """ Adopt WordPress sites where the plugin is installed but not tracked in our database or flagged as manually removed. This handles scenarios like: - Sites copied/migrated from another location - Sites migrated from another server - Sites where the manually_deleted flag was incorrectly set (past bugs) - Sites where the user installed the plugin from wordpress.org """ sites = get_sites_to_adopt() processor = WordPressSiteAdopter(sink, sites) return await processor.run() def get_latest_plugin_version() -> str: """Get the latest version of the imunify-security plugin from the version file.""" try: if not PLUGIN_VERSION_FILE.exists(): logger.error( "Plugin version file does not exist: %s", PLUGIN_VERSION_FILE ) return None return PLUGIN_VERSION_FILE.read_text().strip() except Exception as e: logger.error("Failed to read plugin version file: %s", e) return None async def update_everywhere(sink): """Update the imunify-security plugin on all sites where it is installed.""" latest_version = get_latest_plugin_version() if not latest_version: logger.error("Could not determine latest plugin version") return logger.info( "Updating imunify-security wp plugin to the latest version %s", latest_version, ) updated = set() telemetry_coros = [] with inactivity.track.task("wp-plugin-update"): try: # Get sites with outdated versions outdated_sites = get_outdated_sites(latest_version) logger.info(f"Found {len(outdated_sites)} outdated sites") if not outdated_sites: return # Create SystemConfig once for all users admin_config = SystemConfig() versions = await get_imunify_package_versions() # Group sites by user id sites_by_user = defaultdict(list) for site in outdated_sites: sites_by_user[site.uid].append(site) # Process each user's sites for uid, sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) username = user_info.pw_name except Exception as error: logger.error( "Failed to get username for uid=%d. error=%s", uid, error, ) continue # Get scan data once for all sites of this user ( last_scan_time, next_scan_time, malware_by_site, ) = await _get_scan_data_for_user( sink, user_info, admin_config ) plugin_config = prepare_plugin_config(username) for site in sites: if await remove_site_if_missing(sink, site): continue try: # Check if site still exists if not await cli.is_wordpress_installed(site): logger.info( "WordPress site no longer exists: %s", site ) continue # Prepare scan data scan_data = prepare_scan_data( last_scan_time, next_scan_time, username, site, malware_by_site, versions=versions, ) # Resolve the data dir once; both writes reuse it. data_dir = await ensure_site_data_directory( site, user_info ) # Update the scan data file await update_scan_data_file( site, scan_data, user_info=user_info, data_dir=data_dir, ) # Keep plugin_config.php fresh alongside scan_data # — covers the case where a ConfigUpdate event # was missed (plugin re-installed after the # toggle, first scan after an upgrade, etc). await update_plugin_config_file( site, plugin_config, user_info=user_info, data_dir=data_dir, ) # Now update the plugin await cli.plugin_update(site) updated.add(site) # Get the version after update version = await cli.get_plugin_version(site) if version: # Store original version for comparison original_version = site.version # Update the database with the new version update_site_version(site, version) # Create a new WPSite with updated version site = site.build_with_version(version) # Determine if this is a downgrade is_downgrade = LooseVersion( version ) < LooseVersion(original_version) # Prepare telemetry telemetry_coros.append( telemetry.send_event( sink=sink, event=( "downgraded_by_imunify" if is_downgrade else "updated_by_imunify" ), site=site, version=version, ) ) except Exception as error: logger.error( "Failed to update plugin on site=%s error=%s", site, error, ) logger.info( "Updated imunify-security wp plugin on %d sites", len(updated), ) except asyncio.CancelledError: logger.info( "Update of imunify-security wp plugin was cancelled. Plugin" " was updated on %d sites", len(updated), ) except Exception as error: logger.error( "Error occurred during plugin update. error=%s", error ) raise finally: # Send telemetry await process_telemetry_tasks(telemetry_coros) async def delete_plugin_files(site: WPSite): data_dir = await cli.get_data_dir(site) # Open both target and parent dirs with symlink protection before # performing any destructive operations. try: dir_fd = open_dir_no_symlinks(data_dir) except OSError as exc: if exc.errno == errno.ENOENT: return # directory does not exist — nothing to delete if exc.errno in (errno.ELOOP, errno.ENOTDIR): logger.warning( "Skipping rmtree: data directory %s is a symlink", data_dir ) return raise try: with safe_dir(data_dir.parent) as parent_fd: try: await asyncio.to_thread(rmtree_fd, dir_fd) finally: os.close(dir_fd) dir_fd = -1 # Remove the now-empty directory via the parent fd. os.rmdir(data_dir.name, dir_fd=parent_fd) except BaseException: if dir_fd >= 0: os.close(dir_fd) raise async def remove_from_single_site(site: WPSite, sink, telemetry_coros) -> int: """ Remove the imunify-security plugin from a single site, including all cleanup and telemetry. Returns the number of affected sites (should be 1 if deletion was successful). This function is intended to be protected with asyncio.shield to ensure it completes even if the parent task is cancelled. """ try: # Check if site is still installed and accessible using WP CLI is_installed = await cli.is_plugin_installed(site) if not is_installed: # Plugin is no longer installed. It was removed manually by the user. await process_manually_deleted_plugin( site, time.time(), sink, telemetry_coros ) return 0 # Get the version of the plugin (for telemetry data) version = await cli.get_plugin_version(site) # Uninstall the plugin from WordPress site. await cli.plugin_uninstall(site) # Delete the data files from the site. await delete_plugin_files(site) # Delete the site from database. affected = delete_site(site) # Send telemetry for successful uninstall telemetry_coros.append( telemetry.send_event( sink=sink, event="uninstalled_by_imunify", site=site, version=version, ) ) return affected except Exception as error: # Log any error that occurs during the removal process logger.error("Failed to remove plugin from %s %s", site, error) return 0 async def remove_all_installed(sink): """Remove the imunify-security plugin from all sites where it is installed.""" logger.info("Deleting imunify-security wp plugin") telemetry_coros = [] affected = 0 with inactivity.track.task("wp-plugin-removal"): try: clear_caches() to_remove = get_installed_sites() for site in to_remove: try: affected += await asyncio.shield( remove_from_single_site(site, sink, telemetry_coros) ) except asyncio.CancelledError: logger.info( "Deleting imunify-security wp plugin was cancelled." " Plugin was deleted from %d sites (out of %d)", affected, len(to_remove), ) except Exception as error: logger.error("Error occurred during plugin deleting. %s", error) raise finally: logger.info( "Removed imunify-security wp plugin from %s sites", affected, ) # send telemetry await process_telemetry_tasks(telemetry_coros) async def process_manually_deleted_plugin(site, now, sink, telemetry_coros): """ Process the manually deleted plugin for a single site. Args: site: The site to process. now: The current time. sink: The telemetry/event sink. telemetry_coros: The list of telemetry coroutines to add the event to. The process includes: - marking the site as manually deleted in the database - removing plugin data files - sending telemetry for manual removal """ try: # Mark the site as manually deleted in the database mark_site_as_manually_deleted(site, now) # Remove plugin data files await delete_plugin_files(site) # Send telemetry for manual removal telemetry_coros.append( telemetry.send_event( sink=sink, event="removed_by_user", site=site, version=site.version, ) ) except Exception as error: logger.error( "Failed to process manually deleted plugin for site=%s error=%s", site, error, ) async def tidy_up_manually_deleted( sink, freshly_installed_sites: set[WPSite] = None ): """ Tidy up sites that have been manually deleted by the user. Args: sink: The telemetry/event sink. freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. """ telemetry_coros = [] try: to_mark_as_manually_removed = get_sites_to_mark_as_manually_deleted( freshly_installed_sites ) if to_mark_as_manually_removed: now = time.time() for site in to_mark_as_manually_removed: await process_manually_deleted_plugin( site, now, sink, telemetry_coros ) except Exception as error: logger.error("Error occurred during site tidy up. %s", error) finally: if telemetry_coros: await process_telemetry_tasks(telemetry_coros) async def update_data_on_sites(sink, sites: list[WPSite]): if not sites: return # Create SystemConfig once for all users admin_config = SystemConfig() versions = await get_imunify_package_versions() # Group sites by user id sites_by_user = defaultdict(list) for site in sites: sites_by_user[site.uid].append(site) # Now iterate over the grouped sites for uid, sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) username = user_info.pw_name except Exception as error: logger.error( "Failed to get username for uid=%d. error=%s", uid, error, ) continue ( last_scan_time, next_scan_time, malware_by_site, ) = await _get_scan_data_for_user(sink, user_info, admin_config) plugin_config = prepare_plugin_config(username) for site in sites: if await remove_site_if_missing(sink, site): continue try: # Prepare scan data scan_data = prepare_scan_data( last_scan_time, next_scan_time, username, site, malware_by_site, versions=versions, ) # Resolve the site's data directory once; both writes reuse it. data_dir = await ensure_site_data_directory(site, user_info) # Update the scan data file await update_scan_data_file( site, scan_data, user_info=user_info, data_dir=data_dir ) # Keep plugin_config.php fresh alongside scan_data. await update_plugin_config_file( site, plugin_config, user_info=user_info, data_dir=data_dir ) except Exception as error: logger.error( "Failed to update site data on site=%s error=%s", site, error, ) async def _write_json_php_data_file( site: WPSite, filename: str, data: dict, *, user_info: pwd.struct_passwd | None = None, data_dir: Path | None = None, ) -> None: """Write ``data`` as embedded JSON to ``/``. A caller writing several files into one site's directory can resolve ``user_info`` and ``data_dir`` once and pass them in, so the owner lookup and directory-ensure are not repeated per file. """ if user_info is None: user_info = pwd.getpwuid(site.uid) if data_dir is None: data_dir = await ensure_site_data_directory(site, user_info) php_content = format_php_with_embedded_json(data) write_plugin_data_file_atomically( data_dir / filename, php_content, uid=site.uid, gid=user_info.pw_gid ) async def update_scan_data_file( site: WPSite, scan_data: dict, *, user_info: pwd.struct_passwd | None = None, data_dir: Path | None = None, ): await _write_json_php_data_file( site, "scan_data.php", scan_data, user_info=user_info, data_dir=data_dir, ) async def update_plugin_config_file( site: WPSite, plugin_config: dict, *, user_info: pwd.struct_passwd | None = None, data_dir: Path | None = None, ) -> None: """ Write plugin_config.php for a single WordPress site. Separate file from scan_data.php so a config toggle doesn't force rewriting the malware list, and so the mu-plugin hot path loads only what it needs per request. """ await _write_json_php_data_file( site, "plugin_config.php", plugin_config, user_info=user_info, data_dir=data_dir, ) async def update_plugin_config_on_sites(sites: list[WPSite]) -> int: """ Rewrite plugin_config.php on every managed site in one pass. Used by the ConfigUpdate handler that reacts to WORDPRESS.ai_bot_protection toggles. Writes only plugin_config.php — scan_data.php is untouched, so a toggle doesn't churn the (potentially large) malware payload or wait on a scan cycle. No sink is needed: unlike update_data_on_sites we emit no telemetry here — the per-site write loop just needs local file I/O plus the process-level logger for errors. Returns the number of sites successfully updated so the caller can decide whether to advance its cached state. """ if not sites: return 0 updated = 0 # Group by uid so we look up username once per user, mirroring # update_data_on_sites' pattern and making per-user error isolation # straightforward. sites_by_user: dict[int, list[WPSite]] = defaultdict(list) for site in sites: sites_by_user[site.uid].append(site) for uid, user_sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) username = user_info.pw_name except Exception as error: logger.error( "Failed to get username for uid=%d. error=%s", uid, error, ) continue plugin_config = prepare_plugin_config(username) for site in user_sites: try: await update_plugin_config_file( site, plugin_config, user_info=user_info ) updated += 1 except Exception as error: logger.error( "Failed to update plugin_config.php on site=%s error=%s", site, error, ) return updated async def update_wp_rules_for_site( site: WPSite, user_info: pwd.struct_passwd, wp_rules_php: str, updated: set, failed: set, ) -> None: """ Deploy wp-rules to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd wp_rules_php: Formatted PHP rules content updated: Set to add site to if successful failed: Set to add site to if failed """ gid = user_info.pw_gid try: data_dir = await ensure_site_data_directory(site, user_info) rules_path = data_dir / "rules.php" write_plugin_data_file_atomically( rules_path, wp_rules_php, uid=site.uid, gid=gid ) updated.add(site) logger.info("Updated wp-rules for site %s", site.docroot) except Exception as error: failed.add(site) logger.error( "Failed to update wp-rules for site %s: %s", site.docroot, error, ) async def _deploy_to_sites( sites: list[WPSite], make_task: Callable[ [WPSite, pwd.struct_passwd, set, set], Awaitable[None] ], task_name: str, fingerprint: str, skip_waf_disabled: bool = False, sink=None, ) -> None: """ Run a per-site async deployment over a list of WordPress sites. Groups sites by user, resolves UIDs, then runs tasks concurrently in batches. Args: sites: WordPress sites to deploy to make_task: Callable that creates a coroutine for one site. Signature: (site, user_info, updated_set, failed_set) -> awaitable task_name: Human-readable name for logging and inactivity tracking fingerprint: Sentry fingerprint for user-lookup failures sink: Optional telemetry sink for remove_site_if_missing """ updated = set() failed = set() with inactivity.track.task(task_name): try: start_time = time.time() sites_by_user = defaultdict(list) for site in sites: sites_by_user[site.uid].append(site) tasks = [] for uid, user_sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) username = user_info.pw_name except Exception as error: log_message( "Skipping {task} update for {count} site(s)" " belonging to user {user} because username" " retrieval failed. Reason: {reason}", format_args={ "task": task_name, "count": len(user_sites), "user": uid, "reason": error, }, level="warning", component="wordpress", fingerprint=fingerprint, ) for site in user_sites: failed.add(site) continue if skip_waf_disabled: try: waf_enabled = await is_waf_enabled_for_user(username) except Exception: logger.warning( "Could not check WAF status for user %s," " proceeding with deployment", username, exc_info=True, ) waf_enabled = True if not waf_enabled: logger.info( "WAF disabled for user %s, skipping %d site(s)", username, len(user_sites), ) continue for site in user_sites: if await remove_site_if_missing(sink, site): continue tasks.append(make_task(site, user_info, updated, failed)) max_concurrent = 10 for i in range(0, len(tasks), max_concurrent): batch = tasks[i : i + max_concurrent] await asyncio.gather(*batch, return_exceptions=True) elapsed = time.time() - start_time logger.info( "%s deployment complete. Updated: %d, Failed: %d," " Duration: %.2fs", task_name, len(updated), len(failed), elapsed, ) except asyncio.CancelledError: logger.info( "%s deployment was cancelled. Updated %d sites", task_name, len(updated), ) except Exception as error: logger.error( "Error occurred during %s deployment. error=%s", task_name, error, ) raise async def _deploy_wp_rules_php(wp_rules_php: str, sink=None) -> None: """Deploy pre-formatted wp-rules PHP content to all active WordPress sites.""" clear_caches() installed_sites = get_installed_sites() if not installed_sites: logger.debug("No active WordPress sites found") return def make_task(site, user_info, updated, failed): return update_wp_rules_for_site( site, user_info, wp_rules_php, updated, failed ) await _deploy_to_sites( installed_sites, make_task, task_name="wp-rules", fingerprint="wp-rules-update-skip-user", skip_waf_disabled=True, sink=sink, ) async def update_wp_rules_on_sites(index: Index, is_updated: bool) -> None: """ Hook that runs when wp-rules files are updated. Extracts wp-rules.yaml from wp-rules.zip and deploys to all active WordPress sites. Args: index: Index object for wp-rules is_updated: Whether files were actually updated """ if not Wordpress.SECURITY_PLUGIN_ENABLED: logger.info( "wordpress security plugin not enabled, skipping wp-rules" " deployment" ) return if not is_updated: logger.info("wp-rules not updated, skipping deployment") return logger.info("Starting wp-rules deployment to WordPress sites") wp_rules_data = get_updated_wp_rules_data(index) if not wp_rules_data: logger.error("No valid wp-rules found, skipping deployment") return # Get version and create ruleset dict with version and rules wp_rules_version = get_wp_ruleset_version(index) ruleset_dict = { "version": wp_rules_version, "rules": wp_rules_data, } wp_rules_php = format_php_with_embedded_json(ruleset_dict) await _deploy_wp_rules_php(wp_rules_php) _redeploy_rules_php_lock = asyncio.Lock() # Separate flag is needed because lock.locked() is always True inside the # holder's context, so it cannot indicate whether another caller coalesced. _redeploy_rules_php_pending = False async def redeploy_rules_php() -> None: """ Re-deploy rules.php to all WordPress sites. Used when globally disabled rules change, requiring rules.php to be regenerated with updated rule filtering. Uses a coalescing lock: if a redeployment is already running, the request is merged into the current run rather than starting a duplicate deployment. """ global _redeploy_rules_php_pending if not Wordpress.SECURITY_PLUGIN_ENABLED: logger.info( "wordpress security plugin not enabled, skipping wp-rules" " redeployment" ) return if _redeploy_rules_php_lock.locked(): _redeploy_rules_php_pending = True logger.info("wp-rules redeployment already in progress, coalescing") return async with _redeploy_rules_php_lock: while True: _redeploy_rules_php_pending = False logger.info( "Starting wp-rules redeployment (global disable change)" ) wp_rules_php = await load_wp_rules_php() if not wp_rules_php: logger.warning("Could not load wp-rules for redeployment") return await _deploy_wp_rules_php(wp_rules_php) if not _redeploy_rules_php_pending: break logger.info("Re-running wp-rules redeployment (coalesced request)") async def redeploy_waf_for_all_sites() -> None: """Global WAF turn-on: deploy rules.php and disabled-rules.php (stamping disabled_rules_sync_ts) to all sites, matching install-with-WAF-on. Wraps rather than extends redeploy_rules_php, which is also the global-rule-change path where restamping sync_ts would skip unconsumed changelog actions. """ await redeploy_rules_php() await update_disabled_rules_on_sites() def _remove_waf_files_for_dir(data_dir, docroot: str) -> None: """Remove WAF files from data_dir via a symlink-safe dir fd.""" try: dir_fd = open_dir_no_symlinks(data_dir) except FileNotFoundError: return except OSError as exc: if exc.errno in (errno.ELOOP, errno.ENOTDIR): logger.warning( "Skipping WAF file removal: data dir %s is a symlink", data_dir, ) return logger.error("Failed to open data dir for %s: %s", docroot, exc) return try: for filename in ("rules.php", "disabled-rules.php"): try: os.remove(filename, dir_fd=dir_fd) logger.info( "Removed %s from %s (WAF disabled)", filename, docroot ) except FileNotFoundError: pass except OSError as e: logger.error( "Failed to remove %s from %s: %s", filename, docroot, e ) finally: os.close(dir_fd) async def _remove_waf_files_from_sites(sites: list[WPSite]) -> None: """Remove WAF files (rules.php, disabled-rules.php) from the given sites. Deletion goes through open_dir_no_symlinks + dir_fd so a site owner cannot redirect the root agent's removal via a symlinked data dir, the same symlink-safe pattern as delete_plugin_files. """ for site in sites: try: data_dir = await cli.get_data_dir(site) except Exception as e: logger.error( "Failed to resolve data dir for %s: %s", site.docroot, e ) continue await asyncio.to_thread( _remove_waf_files_for_dir, data_dir, site.docroot ) async def redeploy_waf_for_user(username: str) -> None: """Deploy rules.php and disabled-rules.php for a user's sites (WAF turn-on). Caller must have confirmed WAF is enabled for the user. disabled-rules.php is deployed even if the ruleset fails to load, because it stamps disabled_rules_sync_ts — matching install, which writes it whenever WAF is on regardless of rules.php content. """ if not Wordpress.SECURITY_PLUGIN_ENABLED: return loop = asyncio.get_running_loop() try: user_info = await loop.run_in_executor(None, pwd.getpwnam, username) except KeyError: logger.warning( "User %s not found, skipping WAF rules redeploy", username ) return sites = await loop.run_in_executor(None, get_installed_sites) user_sites = [s for s in sites if s.uid == user_info.pw_uid] if not user_sites: return updated = set() failed = set() wp_rules_php = await load_wp_rules_php() if wp_rules_php: for site in user_sites: await update_wp_rules_for_site( site, user_info, wp_rules_php, updated, failed ) else: logger.warning("Could not load wp-rules for user redeploy") disabled_rules_ts = time.time() dr_updated = set() dr_failed = set() for site in user_sites: await update_disabled_rules_for_site( site, user_info, disabled_rules_ts, dr_updated, dr_failed ) logger.info( "Redeployed WAF artifacts for user %s: rules %d ok/%d failed," " disabled-rules %d ok/%d failed", username, len(updated), len(failed), len(dr_updated), len(dr_failed), ) async def remove_waf_rules_for_user(username: str) -> None: """Remove WAF files from all sites belonging to a user.""" loop = asyncio.get_running_loop() try: user_info = await loop.run_in_executor(None, pwd.getpwnam, username) except KeyError: logger.warning( "User %s not found, skipping WAF rules removal", username ) return sites = await loop.run_in_executor(None, get_installed_sites) user_sites = [s for s in sites if s.uid == user_info.pw_uid] await _remove_waf_files_from_sites(user_sites) async def remove_waf_rules_for_all_sites() -> None: """Remove WAF files from all installed sites (global WAF disable).""" loop = asyncio.get_running_loop() sites = await loop.run_in_executor(None, get_installed_sites) logger.info( "Global WAF disabled, removing WAF files from %d site(s)", len(sites), ) await _remove_waf_files_from_sites(sites) async def apply_waf_default_change() -> None: """Redeploy/remove rules.php for users without an explicit waf_enabled override.""" global _apply_waf_default_pending if not Wordpress.SECURITY_PLUGIN_ENABLED: return if _apply_waf_default_lock.locked(): _apply_waf_default_pending = True logger.info("waf_default change already in progress, coalescing") return async with _apply_waf_default_lock: while True: _apply_waf_default_pending = False if not _get_global_waf_enabled(): return new_default = _get_waf_default() usernames = await hosting_panel.HostingPanel().get_users() loop = asyncio.get_running_loop() for username in usernames: try: if await loop.run_in_executor( None, _user_has_explicit_waf_override_sync, username, ): continue if new_default: await redeploy_waf_for_user(username) else: await remove_waf_rules_for_user(username) except Exception as e: logger.warning( "Failed to apply waf_default change for user %s: %s", username, e, ) if not _apply_waf_default_pending: break logger.info("Re-running waf_default change (coalesced request)") def generate_disabled_rules_php(domain: str, timestamp: float) -> str: """ Generate the disabled-rules.php content for a specific domain. Only includes domain-specific disabled rules. Globally disabled rules are handled separately by filtering them out of rules.php. Args: domain: The domain to generate disabled rules for timestamp: Unix timestamp to embed in the file Returns: PHP file content string """ disabled_rule_ids = WPDisabledRule.get_domain_disabled( domain, include_global=False ) data = { "ts": timestamp, "rules": sorted(disabled_rule_ids), } return format_php_with_embedded_json(data) async def update_disabled_rules_for_site( site: WPSite, user_info: pwd.struct_passwd, timestamp: float, updated: set, failed: set, ) -> None: """ Deploy disabled-rules.php to a single WordPress site and track the result. Args: site: WordPress site to deploy to user_info: User information from pwd timestamp: Unix timestamp for both file content and DB record updated: Set to add site to if successful failed: Set to add site to if failed """ gid = user_info.pw_gid try: data_dir = await ensure_site_data_directory(site, user_info) disabled_rules_path = data_dir / "disabled-rules.php" php_content = generate_disabled_rules_php(site.domain, timestamp) write_plugin_data_file_atomically( disabled_rules_path, php_content, uid=site.uid, gid=gid ) WordpressSite.update(disabled_rules_sync_ts=timestamp).where( WordpressSite.docroot == site.docroot ).execute() updated.add(site) logger.info("Updated disabled-rules for site %s", site.docroot) except Exception as error: failed.add(site) logger.error( "Failed to update disabled-rules for site %s: %s", site.docroot, error, ) async def update_disabled_rules_on_sites( domains: list[str] | None = None, sink=None, ) -> None: """ Deploy disabled-rules.php to WordPress sites. If domains are specified, only updates sites for those domains. If domains is None, updates all installed sites (e.g., after a global disable/enable). Args: domains: List of domains to update, or None for all sites sink: Optional telemetry sink for remove_site_if_missing """ if not Wordpress.SECURITY_PLUGIN_ENABLED: logger.info( "wordpress security plugin not enabled, skipping disabled-rules" " deployment" ) return logger.info("Starting disabled-rules deployment to WordPress sites") clear_caches() if domains: sites = get_installed_sites_by_domains(domains) else: sites = get_installed_sites() if not sites: logger.info("No WordPress sites found for disabled-rules deployment") return def make_task(site, user_info, updated, failed): return update_disabled_rules_for_site( site, user_info, time.time(), updated, failed ) await _deploy_to_sites( sites, make_task, task_name="disabled-rules", fingerprint="disabled-rules-update-skip-user", skip_waf_disabled=True, sink=sink, ) async def update_auth_everywhere(sink=None): """Update auth.php files for all existing WordPress sites.""" logger.info("Updating auth.php files for existing WordPress sites") updated = set() failed = set() with inactivity.track.task("wp-auth-update"): try: clear_caches() # Get all installed sites from db installed_sites = get_installed_sites() if not installed_sites: logger.info("No installed WordPress sites found") return sites_by_user = defaultdict(list) for site in installed_sites: sites_by_user[site.uid].append(site) # Process users concurrently tasks = [] for uid, sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) except Exception as error: log_message( "Skipping auth update for WordPress sites on" " {count} site(s) because they belong to user" " {user} and it is not possible to retrieve" " username for this user. Reason: {reason}", format_args={ "count": len(sites), "user": uid, "reason": error, }, level="warning", component="wordpress", fingerprint="wp-plugin-auth-update-skip-user", ) continue for site in sites: if await remove_site_if_missing(sink, site): continue task = update_site_auth(site, user_info, updated, failed) tasks.append(task) # Run all site updates concurrently with a reasonable limit # Adjust max_concurrent based on your system's I/O capacity max_concurrent = 10 for i in range(0, len(tasks), max_concurrent): batch = tasks[i : i + max_concurrent] await asyncio.gather(*batch, return_exceptions=True) logger.info( "Updated auth.php files for %d WordPress sites, %d failed", len(updated), len(failed), ) except asyncio.CancelledError: logger.info( "Auth update for WordPress sites was cancelled. Auth was" " updated for %d sites", len(updated), ) except Exception as error: logger.error("Error occurred during auth update. error=%s", error) raise async def update_site_auth(site, user_info, updated, failed): """Process authentication setup for a single site.""" try: await setup_site_authentication(site, user_info) updated.add(site) except Exception as error: failed.add(site) logger.error( "Failed to update auth for site=%s error=%s", site, error, ) async def remove_site_if_missing(sink, site: WPSite) -> bool: """ Checks if the site directory exists. If not, removes the site from the local database and sends a 'site_removed' telemetry event only if deletion is successful. Returns True if the site was removed (directory missing), False otherwise. Parameters: sink: The telemetry/event sink. site: The WPSite object to check and potentially remove. Side effect: If the site is missing and successfully deleted from database, a telemetry event will be sent. """ if os.path.isdir(site.docroot): return False # Attempt to delete the site from the database first rows_deleted = delete_site(site) # Only send telemetry if the deletion was successful (at least one row was deleted) if rows_deleted > 0: if sink is not None: await telemetry.send_event( sink=sink, event="site_removed", site=site, version=site.version, ) else: logger.warning( "Failed to delete missing site %s from database, no rows affected", site, ) log_message( "Failed to delete missing site {site} from database", format_args={"site": site}, level="warning", component="wordpress", fingerprint="wp-plugin-site-delete-failed", ) return True async def fix_site_data_file_permissions( site: WPSite, file_permissions: int ) -> bool: """Fix data file permissions for a single WordPress site.""" try: data_dir = await cli.get_data_dir(site) try: dir_fd = open_dir_no_symlinks(data_dir) except OSError as exc: if exc.errno in (errno.ENOENT, errno.ELOOP, errno.ENOTDIR): return False raise try: # Fix directory permissions via fd. current_dir_mode = os.stat(dir_fd).st_mode & 0o777 if current_dir_mode != 0o750: os.chmod(dir_fd, 0o750) for file_name in [ "scan_data.php", "plugin_config.php", "auth.php", "rules.php", "disabled-rules.php", ]: try: with open_nofollow(file_name, dir_fd=dir_fd) as file_fd: st = os.fstat(file_fd) if st.st_mode & 0o777 != file_permissions: os.chmod(file_fd, file_permissions) except FileNotFoundError: continue except OSError as exc: if exc.errno == errno.ELOOP: logger.warning( "Skipping chmod: %s/%s is a symlink", data_dir, file_name, ) continue raise finally: os.close(dir_fd) return True except Exception as error: logger.error( "Failed to fix permissions for site=%s error=%s", site, error, ) return False async def fix_data_file_permissions_everywhere(sink): """ Fix data file permissions for all WordPress sites with imunify-security plugin installed. Args: sink: The telemetry/event sink """ fixed = set() failed = set() with inactivity.track.task("wp-plugin-fix-permissions"): try: clear_caches() # Get all installed sites installed_sites = get_installed_sites() if not installed_sites: return # Determine file permissions based on hosting panel from defence360agent.subsys.panels.hosting_panel import ( HostingPanel, ) from defence360agent.subsys.panels.plesk import Plesk file_permissions = ( 0o440 if HostingPanel().NAME == Plesk.NAME else 0o400 ) # Process sites for site in installed_sites: if await remove_site_if_missing(sink, site): continue success = await fix_site_data_file_permissions( site, file_permissions ) if success: fixed.add(site) else: failed.add(site) logger.info( "Fixed data file permissions for %d WordPress sites, %d" " failed", len(fixed), len(failed), ) except asyncio.CancelledError: logger.info( "Fixing data file permissions was cancelled. Permissions were" " fixed for %d sites", len(fixed), ) except Exception as error: logger.error( "Error occurred during permission fixing. error=%s", error ) class WordPressSiteInstaller: """ Handles installation of imunify-security plugin on WordPress sites. This class processes WordPress sites and installs the imunify-security plugin, including setting up authentication, scan data files, and rules. """ install_plugin = True telemetry_event = "installed_by_imunify" task_name = "wp-plugin-installation" log_fingerprint_skip_user = "wp-plugin-install-skip-user" messages = { "start": "Installing imunify-security wp plugin", "complete": "Installed imunify-security wp plugin on {count} sites", "found": "Found {count} site(s) for installation", "error": "Failed to install plugin to site={site} error={error}", "cancelled": ( "Installation of imunify-security wp plugin was cancelled. " "Plugin was installed for {count} sites" ), "exception": ( "Error occurred during plugin installation. error={error}" ), "skip_user": ( "Skipping installation of WordPress plugin on " "{count} site(s) because they belong to user " "{user} and it is not possible to retrieve " "username for this user. Reason: {reason}" ), } def __init__(self, sink, sites): self.sink = sink self.sites = sites self.processed = set() self.authenticated = set() self.rules_installed = set() self.failed_rules_updates = set() self.disabled_rules_installed = set() self.failed_disabled_rules_updates = set() self.disabled_rules_ts: float | None = None self.failed_auth = set() self._current_site: WPSite | None = None async def is_site_ready(self, site): """ Check if site is ready for processing. Override in subclasses to implement different readiness checks. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for processing, False otherwise. """ is_wordpress_installed = await cli.is_wordpress_installed(site) if not is_wordpress_installed: logger.warning( "WordPress site is not accessible using WP CLI. site=%s", site, ) log_message( "WordPress site is not accessible using WP CLI. site={site}", format_args={"site": site}, level="warning", component="wordpress", fingerprint="wp-plugin-cli-not-accessible", ) return False return True def _record_processed_site(self, site, version): """ Record a successfully processed site and persist it to the database. Each site is inserted immediately so that it is tracked in the DB at all times — even if the overall installation loop is cancelled mid-run. Override in subclasses to implement different recording logic. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. """ self.processed.add(site) insert_installed_sites({site}) self._stamp_disabled_rules_sync_ts(site) async def _revert_in_flight_site(self): """Revert the site that was mid-processing when cancellation occurred. Deletes data files and, if this processor installs plugins, attempts to uninstall the partially-installed plugin. Each step runs independently so one failure doesn't skip the other. """ site = self._current_site self._current_site = None try: await delete_plugin_files(site) except Exception as error: logger.warning( "Failed to delete data files for in-flight site %s: %s", site, error, ) if self.install_plugin: await cli.try_plugin_uninstall(site) def _stamp_disabled_rules_sync_ts(self, site: WPSite) -> None: """ Stamp disabled_rules_sync_ts for a single site after it has been inserted into the DB. Called from ``_record_processed_site`` so the DB row already exists. """ if site not in self.disabled_rules_installed: return if self.disabled_rules_ts is None: return WordpressSite.update( disabled_rules_sync_ts=self.disabled_rules_ts ).where(WordpressSite.docroot == site.docroot).execute() async def run(self): """ Process WordPress sites for imunify-security plugin operations. Returns: set: The set of successfully processed sites. """ logger.info(self.messages["start"]) telemetry_tasks = [] with inactivity.track.task(self.task_name): try: clear_caches() if not self.sites: logger.info("No WordPress sites found, nothing to do") return self.processed logger.info( self.messages["found"].format(count=len(self.sites)) ) # Create SystemConfig once for all users admin_config = SystemConfig() # Create wp rules once for all users wp_rules_php = await load_wp_rules_php() # Always set the timestamp, even when no disabled rules # currently exist: previously disabled-then-enabled rules # require deploying an empty disabled-rules.php. self.disabled_rules_ts = time.time() versions = await get_imunify_package_versions() # Group sites by user id sites_by_user = defaultdict(list) for site in self.sites: sites_by_user[site.uid].append(site) # Now iterate over the grouped sites for uid, sites in sites_by_user.items(): try: user_info = pwd.getpwuid(uid) username = user_info.pw_name except Exception as error: log_message( self.messages["skip_user"], format_args={ "count": len(sites), "user": uid, "reason": error, }, level="warning", component="wordpress", fingerprint=self.log_fingerprint_skip_user, ) continue try: waf_enabled = await is_waf_enabled_for_user(username) except Exception: logger.warning( "Could not check WAF status for user %s," " proceeding with deployment", username, exc_info=True, ) waf_enabled = True if not waf_enabled: logger.info( "WAF disabled for user %s, skipping WAF" " rules deployment for %d site(s)", username, len(sites), ) ( last_scan_time, next_scan_time, malware_by_site, ) = await _get_scan_data_for_user( self.sink, user_info, admin_config ) plugin_config = prepare_plugin_config(username) for site in sites: if await remove_site_if_missing(self.sink, site): continue try: # Check if site is ready for processing (WP CLI accessible + other checks) if not await self.is_site_ready(site): continue self._current_site = site # Prepare scan data scan_data = prepare_scan_data( last_scan_time, next_scan_time, username, site, malware_by_site, versions=versions, ) # Resolve the data directory once; the scan-data # and plugin-config writes below reuse it. data_dir = await ensure_site_data_directory( site, user_info ) # Create data files (scan data, plugin config, auth token) await update_scan_data_file( site, scan_data, user_info=user_info, data_dir=data_dir, ) await update_plugin_config_file( site, plugin_config, user_info=user_info, data_dir=data_dir, ) await update_site_auth( site, user_info, self.authenticated, self.failed_auth, ) # Install rules — skip when WAF is disabled for # this user (global off or per-user override). if wp_rules_php and waf_enabled: await update_wp_rules_for_site( site, user_info, wp_rules_php, self.rules_installed, self.failed_rules_updates, ) # Install disabled rules if waf_enabled: await update_disabled_rules_for_site( site, user_info, self.disabled_rules_ts, self.disabled_rules_installed, self.failed_disabled_rules_updates, ) # Install the plugin if self.install_plugin: await cli.plugin_install(site) # Get the version of the plugin version = await cli.get_plugin_version(site) if version: site = WPSite.build_with_version(site, version) # Record the processed site self._record_processed_site(site, version) self._current_site = None telemetry_tasks.append( asyncio.create_task( telemetry.send_event( sink=self.sink, event=self.telemetry_event, site=site, version=version, ) ) ) except Exception as error: self._current_site = None logger.error( self.messages["error"].format( site=site, error=repr(error) ) ) logger.info( self.messages["complete"].format(count=len(self.processed)) ) if self.failed_auth: logger.warning( "Failed to authenticate %d sites", len(self.failed_auth), ) if self.failed_rules_updates: logger.warning( "Failed to install wp-rules on %d sites", len(self.failed_rules_updates), ) if self.failed_disabled_rules_updates: logger.warning( "Failed to install disabled-rules on %d sites", len(self.failed_disabled_rules_updates), ) except asyncio.CancelledError: if self._current_site: await self._revert_in_flight_site() logger.info( self.messages["cancelled"].format( count=len(self.processed) ) ) except Exception as error: logger.error( self.messages["exception"].format(error=repr(error)) ) raise finally: if telemetry_tasks: results = await asyncio.gather( *telemetry_tasks, return_exceptions=True ) for result in results: if isinstance(result, Exception): logger.warning( "Failed to send telemetry: %s", result ) return self.processed class WordPressSiteAdopter(WordPressSiteInstaller): """ Handles adoption of existing WordPress sites with imunify-security plugin. Adoption is a special case of installation where the site already has the plugin installed but is not tracked in our database. """ install_plugin = False telemetry_event = "site_found" task_name = "wp-plugin-adoption" log_fingerprint_skip_user = "wp-plugin-adopt-skip-user" messages = { "start": "Adopting imunify-security wp plugin", "complete": "Adopted imunify-security wp plugin on {count} sites", "found": "Found {count} site(s) for adoption", "error": "Failed to adopt plugin to site={site} error={error}", "cancelled": ( "Adoption of imunify-security wp plugin was cancelled. " "Plugin was adopted for {count} sites" ), "exception": "Error occurred during plugin adoption. error={error}", "skip_user": ( "Skipping adoption of WordPress plugin on " "{count} site(s) because they belong to user " "{user} and it is not possible to retrieve " "username for this user. Reason: {reason}" ), } def __init__(self, sink, sites): super().__init__(sink, sites) # Load existing docroots from database for adoption logic self.existing_docroots = { r.docroot for r in WordpressSite.select(WordpressSite.docroot) } def _record_processed_site(self, site, version): """ Record a successfully adopted site and persist it immediately. For adoption, sites that already exist in the database (flagged as manually deleted) have their flag cleared. New sites are inserted into the database right away. Args: site: The WordPress site that was processed. version: The plugin version installed on the site. """ self.processed.add(site) if site.docroot in self.existing_docroots: # Site exists in DB but is flagged - clear flag clear_manually_deleted_flag(site) update_site_identity(site) if version: update_site_version(site, version) else: insert_installed_sites({site}) self._stamp_disabled_rules_sync_ts(site) async def is_site_ready(self, site): """ Check if site is ready for adoption. Args: site: The WordPress site to check. Returns: bool: True if the site is ready for adoption, False otherwise. """ if not await super().is_site_ready(site): return False # Verify plugin is actually installed is_installed = await cli.is_plugin_installed(site) if not is_installed: logger.warning( "Plugin not installed on site %s, skipping adoption", site, ) return False return True defence360agent/wordpress/proxy_auth.py0000644000000000000000000001211000000000000015345 0ustar import asyncio import logging import os import pwd import secrets from datetime import datetime, timedelta from functools import lru_cache from pathlib import Path from defence360agent.utils import atomic_rewrite from defence360agent.wordpress.utils import ( ensure_site_data_directory, format_php_with_embedded_json, write_plugin_data_file_atomically, ) logger = logging.getLogger(__name__) DEFAULT_TOKEN_EXPIRATION = timedelta(hours=72) JWT_SECRET_PATH = "/etc/imunify-agent-proxy/jwt-secret" JWT_SECRET_PATH_OLD = "/etc/imunify-agent-proxy/jwt-secret.old" PROXY_SERVICE_NAME = "imunify-agent-proxy" SECRET_EXPIRATION_TTL = timedelta(days=7) def is_secret_expired(): try: stat = os.stat(JWT_SECRET_PATH) except FileNotFoundError: st_mtime = 0.0 else: st_mtime = stat.st_mtime # NOTE: timedelta(days=7).seconds == 0 (the .seconds attribute only holds # the sub-day component; .days holds the rest). Use .total_seconds() so # the 7-day TTL is honored. return ( datetime.now().timestamp() - st_mtime > SECRET_EXPIRATION_TTL.total_seconds() ) async def rotate_secret(): """Rotate the proxy JWT secret on disk: backup current to .old and write a fresh 32-byte secret atomically. Invalidates the in-process cache so subsequent generate_token() calls read the new secret. """ secret_path = Path(JWT_SECRET_PATH) try: logger.info("Rotating proxy auth secret") stub_secret = secrets.token_bytes(32) secret_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True) secret_path.touch(mode=0o600) atomic_rewrite( secret_path, stub_secret, uid=-1, backup=str(JWT_SECRET_PATH_OLD), permissions=0o600, ) load_secret_from_file.cache_clear() except Exception as e: logger.error( "Got error while rotating the secret: %s", e, exc_info=True ) @lru_cache(1) def load_secret_from_file() -> bytes: """Load JWT secret from the configured file path.""" try: with open(JWT_SECRET_PATH, "rb") as f: return f.read().strip() except FileNotFoundError: logger.error("JWT secret file not found at %s", JWT_SECRET_PATH) raise except Exception as e: logger.error("Failed to read JWT secret: %s", e) raise def generate_token(username: str, docroot: str) -> str: """ Generate a JWT token for the given username and docroots. Args: username: The username for the token docroot: document root paths the user has access to Returns: The JWT token string """ exp_time = datetime.utcnow() + DEFAULT_TOKEN_EXPIRATION claims = {"exp": exp_time, "username": username, "site_path": docroot} try: # jwt package is a heavy dependency (relying on native libraries) # that is not needed in all execution paths. # in order to save some RAM, jwt is only imporded when it's actually needed. import jwt token = jwt.encode(claims, load_secret_from_file(), algorithm="HS256") return token except Exception as e: logger.error("Failed to generate JWT token: %s", e) raise async def create_auth_php_file(site, token: str, uid, gid: int) -> None: """ Create the auth.php file in the site's imunify-security directory. Args: site: WPSite instance token: JWT token string uid, gid: int used for file creation """ try: # Get user_info to pass to ensure_site_data_directory user_info = pwd.getpwuid(uid) # Ensure data directory exists with protection (this also ensures directory listing protection) data_dir = await ensure_site_data_directory(site, user_info) auth_file_path = data_dir / "auth.php" # Use helper function to format PHP with embedded JSON auth_data = {"token": token} php_content = format_php_with_embedded_json(auth_data) # Run the file write operation in a thread pool await asyncio.to_thread( write_plugin_data_file_atomically, auth_file_path, php_content, uid, gid, ) logger.info( "Created auth.php file for site %s at %s", site, auth_file_path ) except Exception as e: logger.error("Failed to create auth.php file for site %s: %s", site, e) raise async def setup_site_authentication( site, user_info: pwd.struct_passwd ) -> None: """ Set up authentication for a site by creating JWT token and auth.php file. Args: site: WPSite instance user_info: pwd.struct_passwd data """ try: token = generate_token(user_info.pw_name, str(site.docroot)) await create_auth_php_file( site, token, user_info.pw_uid, user_info.pw_gid ) logger.info("Successfully set up authentication for site %s", site) except Exception as e: logger.error( "Failed to set up authentication for site %s: %s", site, e ) raise defence360agent/wordpress/site_repository.py0000644000000000000000000004374300000000000016426 0ustar import asyncio import logging import pwd from pathlib import Path from peewee import SqliteDatabase, OperationalError, fn from defence360agent.utils import retry_on from defence360agent.model.wordpress import WPSite, WordpressSite from defence360agent.wordpress.constants import PLUGIN_SLUG logger = logging.getLogger(__name__) COMPONENTS_DB_PATH = Path( "/var/lib/cloudlinux-app-version-detector/components_versions.sqlite3" ) def get_sites_by_path(path: str) -> list[WPSite]: """ Get a list of WordPress sites that match the given path. Args: path: The path to search for WordPress sites. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A list of WPSite objects that match the path. """ if not COMPONENTS_DB_PATH.exists(): logger.error( "App detector database '%s' couldn't be found.", str(COMPONENTS_DB_PATH), ) return list() cursor = SqliteDatabase(COMPONENTS_DB_PATH).execute_sql( f""" WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs all_wp_sites AS ( SELECT wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE '{path}%' AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ) -- For each real_path, keep only the entry from the latest report SELECT real_path, domain, uid FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) """ ) return [ WPSite(docroot=row[0], domain=row[1], uid=int(row[2])) for row in cursor.fetchall() ] def get_sites_for_user(user_info: pwd.struct_passwd) -> list[str]: """ Get a set of paths to WordPress sites belonging to a particular user. Paths are sorted by their length to make sure that the main site is the last one in the list. The data is pulled from the app-version-detector database. Args: user_info: The user info with ID to get sites for. Returns: A list of paths to WordPress sites. """ if not COMPONENTS_DB_PATH.exists() or user_info is None: logger.error( "App detector database '%s' couldn't be found.", str(COMPONENTS_DB_PATH), ) return list() if user_info is None: logger.error( "No user info provided for getting sites", ) return list() cursor = SqliteDatabase(COMPONENTS_DB_PATH).execute_sql( f""" WITH latest_reports AS ( SELECT MAX(id) as id, dir FROM report WHERE uid = {user_info.pw_uid} GROUP BY dir ) SELECT wp.real_path FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' GROUP BY wp.real_path ORDER BY length(wp.real_path) DESC """ ) return [row[0] for row in cursor.fetchall()] def get_sites_without_plugin() -> set[WPSite]: """ Get a set of wp sites where imunify-security plugin is not installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is not installed. """ if not COMPONENTS_DB_PATH.exists(): logger.error( "App detector database '%s' couldn't be found.", str(COMPONENTS_DB_PATH), ) return set() cursor = SqliteDatabase(COMPONENTS_DB_PATH).execute_sql( f""" WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE NOT EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_{PLUGIN_SLUG.replace("-", "_")}' ) """ ) return { WPSite(docroot=row[0], domain=row[1], uid=int(row[2])) for row in cursor.fetchall() } def get_sites_to_install() -> set[WPSite]: """ Get a set of WordPress sites where we need to install the plugin. This is determined by finding sites that don't have the plugin installed and are not already tracked in our database. Returns: A set of WPSite objects where the plugin needs to be installed. """ sites_without_plugin = get_sites_without_plugin() existing_docroots = { r.docroot for r in WordpressSite.select(WordpressSite.docroot) } return { s for s in sites_without_plugin if s.docroot not in existing_docroots } def insert_installed_sites(sites: set[WPSite]) -> None: """ Insert a set of installed WordPress sites into the database. This is used to track which sites have the plugin installed. Args: sites: A set of WPSite objects representing sites where the plugin was installed. """ if not sites: return WordpressSite.insert_many( [ { "domain": site.domain, "docroot": site.docroot, "uid": site.uid, "version": site.version, "manually_deleted_at": None, } for site in sites ] ).execute() def get_outdated_sites(latest_version: str) -> list[WPSite]: """ Get a list of WordPress sites that have outdated plugin versions. Args: latest_version: The latest available plugin version to compare against. Returns: A list of WPSite objects that have versions older than latest_version. """ if not latest_version: logger.error( "Cannot get outdated sites without a valid latest version" ) return [] return [ WPSite.from_wordpress_site(r) for r in WordpressSite.select().where( WordpressSite.manually_deleted_at.is_null(), WordpressSite.version != latest_version, ) ] def mark_site_as_manually_deleted(site: WPSite, timestamp: float) -> None: """ Mark a WordPress site as manually deleted in the database. Args: site: The WPSite object to mark as deleted timestamp: The timestamp when the site was deleted """ logger.info( "Mark site %s as manually deleted at %s (WP-Plugin removed)", site, timestamp, ) ( WordpressSite.update(manually_deleted_at=timestamp) .where(WordpressSite.docroot == site.docroot) .execute() ) def get_sites_to_mark_as_manually_deleted( freshly_installed_sites: set[WPSite] = None, ) -> set[WPSite]: """ Get a set of WordPress sites that should be marked as manually deleted. These are sites that are in our database but no longer have the plugin installed. Args: freshly_installed_sites: Optional set of sites that were just installed and should be excluded from being marked as manually deleted to avoid race conditions. Returns: set[WPSite]: A set of WordPress sites that should be marked as manually deleted """ # Get sites without plugin from AVD database sites_without_plugin = get_sites_without_plugin() docroots_without_plugin = {s.docroot for s in sites_without_plugin} # Get sites from our database that haven't been marked as manually deleted active_db_sites = { r.docroot: WPSite.from_wordpress_site(r) for r in WordpressSite.select().where( WordpressSite.manually_deleted_at.is_null() ) } # Match by docroot only — AVD may report different domain/uid than our DB docroots_to_mark = docroots_without_plugin & set(active_db_sites) # Filter out freshly installed sites to avoid race condition with AppVersionDetector if freshly_installed_sites: docroots_to_mark -= {s.docroot for s in freshly_installed_sites} return {active_db_sites[d] for d in docroots_to_mark} def update_site_version(site: WPSite, version: str) -> None: """ Update the version of a WordPress site in the database. Args: site: The WPSite object to update version: The new version to set """ WordpressSite.update(version=version).where( WordpressSite.docroot == site.docroot ).execute() def update_site_identity(site: WPSite) -> None: """ Update the domain and uid of a WordPress site in the database to match what AVD currently reports. Args: site: The WPSite object with the current domain and uid from AVD. """ WordpressSite.update(domain=site.domain, uid=site.uid).where( WordpressSite.docroot == site.docroot ).execute() def get_installed_sites_paginated( uid: int | None = None, limit: int | None = None, offset: int = 0, ) -> tuple[int, list[WPSite]]: """ Get active installed WordPress sites with optional filtering and pagination. Args: uid: Optional user ID to filter sites by owner limit: Maximum number of sites to return offset: Number of sites to skip Returns: Tuple of (total_count, paginated_sites) """ query = WordpressSite.select().where( WordpressSite.manually_deleted_at.is_null(True) ) if uid is not None: query = query.where(WordpressSite.uid == uid) total_count = query.count() if limit is not None: query = query.limit(limit) if offset > 0: query = query.offset(offset) sites = [WPSite.from_wordpress_site(site) for site in query] return total_count, sites def count_installed_sites_by_uid() -> dict[int, int]: """ Count active installed WordPress sites per owner uid in one query. Mirrors get_installed_sites_paginated's active-site filter (manually_deleted_at IS NULL). Uids with no active sites are absent from the result rather than mapped to 0. Returns: Mapping of uid -> number of active installed sites. """ query = ( WordpressSite.select( WordpressSite.uid, fn.COUNT(WordpressSite.docroot).alias("count"), ) .where(WordpressSite.manually_deleted_at.is_null(True)) .group_by(WordpressSite.uid) .dicts() ) return {row["uid"]: row["count"] for row in query} def get_installed_sites() -> list[WPSite]: """ Get a list of active installed WordPress sites. These are sites that haven't been marked as manually deleted. Returns: A list of WPSite objects representing non-deleted sites. """ _, sites = get_installed_sites_paginated() return sites def get_installed_sites_by_domains(domains: list[str]) -> list[WPSite]: """ Get active installed WordPress sites filtered by domain names. Args: domains: List of domain names to filter by Returns: List of WPSite objects matching the given domains """ if not domains: return [] return [ WPSite.from_wordpress_site(r) for r in WordpressSite.select().where( WordpressSite.manually_deleted_at.is_null(True), WordpressSite.domain.in_(domains), ) ] async def sleep_on_error(exception, attempt): await asyncio.sleep(0.5) @retry_on( OperationalError, max_tries=3, silent=True, log=False, on_error=sleep_on_error, ) def delete_site(site: WPSite) -> int: """ Delete a WordPress site from the database with retry logic. Will retry up to 3 times on database operational errors with 0.5s delay between attempts. Args: site: The WPSite object to delete Returns: The number of rows affected by the delete operation """ return ( WordpressSite.delete() .where(WordpressSite.docroot == site.docroot) .execute() ) def get_sites_with_plugin() -> set[WPSite]: """ Get a set of WordPress sites where the imunify-security plugin is installed. The data is pulled from the app-version-detector database. Note: The same WordPress site (real_path) can appear in multiple reports if it was scanned directly and also as part of a parent folder scan. We use only the entry from the latest report for each real_path. Returns: A set of WPSite objects where the plugin is installed. """ if not COMPONENTS_DB_PATH.exists(): logger.error( "App detector database '%s' couldn't be found.", str(COMPONENTS_DB_PATH), ) return set() cursor = SqliteDatabase(COMPONENTS_DB_PATH).execute_sql( f""" WITH latest_reports AS ( SELECT id, uid, domain, dir FROM report WHERE id IN ( SELECT MAX(id) FROM report WHERE domain IS NOT NULL AND domain != '' GROUP BY dir ) ), -- Get all WordPress sites with their report IDs. -- The real_path LIKE guard filters out orphaned apps rows -- left behind when AVD rescans and rebuilds the report table. all_wp_sites AS ( SELECT wp.id as wp_id, wp.real_path, lr.domain, lr.uid, lr.id as report_id FROM apps AS wp INNER JOIN latest_reports AS lr ON wp.report_id = lr.id WHERE wp.title = 'wp_core' AND wp.parent_id IS NULL AND wp.real_path LIKE RTRIM(lr.dir, '/') || '%' ), -- For each real_path, keep only the entry from the latest report latest_wp_sites AS ( SELECT wp_id, real_path, domain, uid, report_id FROM all_wp_sites WHERE (real_path, report_id) IN ( SELECT real_path, MAX(report_id) FROM all_wp_sites GROUP BY real_path ) ) SELECT real_path, domain, uid FROM latest_wp_sites lws WHERE EXISTS ( SELECT 1 FROM apps AS plugin WHERE plugin.parent_id = lws.wp_id AND plugin.title = 'wp_plugin_{PLUGIN_SLUG.replace("-", "_")}' ) """ ) return { WPSite(docroot=row[0], domain=row[1], uid=int(row[2])) for row in cursor.fetchall() } def get_sites_to_adopt() -> set[WPSite]: """ Get a set of WordPress sites that should be adopted. These are sites where the plugin is installed but either: - Not tracked in our database (e.g., copied/migrated sites) - Flagged as manually removed (from past bugs or manual reinstall) Returns: A set of WPSite objects that should be adopted. """ sites_with_plugin = get_sites_with_plugin() # Compare by docroot only — AVD may report different domain/uid than our DB tracked_docroots = { r.docroot for r in WordpressSite.select(WordpressSite.docroot).where( WordpressSite.manually_deleted_at.is_null(True) ) } return {s for s in sites_with_plugin if s.docroot not in tracked_docroots} def clear_manually_deleted_flag(site: WPSite) -> None: """ Clear the manually_deleted_at flag for a WordPress site. This is used when adopting a site that was previously marked as manually deleted. Args: site: The WPSite object to clear the flag for """ logger.info( "Clearing manually_deleted_at flag for site %s (plugin found)", site, ) ( WordpressSite.update(manually_deleted_at=None) .where(WordpressSite.docroot == site.docroot) .execute() ) defence360agent/wordpress/telemetry.py0000644000000000000000000000103500000000000015161 0ustar import logging from defence360agent.contracts.messages import MessageType from defence360agent.model.wordpress import WPSite logger = logging.getLogger(__name__) async def send_event(sink, event: str, site: WPSite, version: str = None): if version is None: version = "1.0.0" await sink.process_message( MessageType.WordpressPluginTelemetry( event=event, domain=site.domain, site_path=site.docroot, user=site.uid, plugin_version=version, ) ) defence360agent/wordpress/utils.py0000644000000000000000000005422200000000000014315 0ustar import errno import json import logging import os import pwd import shlex import subprocess from collections import defaultdict from datetime import datetime, timedelta from functools import cache, lru_cache from pathlib import Path from typing import Optional from defence360agent.contracts.config import ( choose_value_from_config, MalwareScanScheduleInterval as Interval, ) from defence360agent.contracts.license import LicenseCLN from defence360agent.subsys.panels.hosting_panel import HostingPanel from defence360agent.subsys.panels.plesk import Plesk from defence360agent.utils import ( IMUNIFY_PACKAGE_NAMES, async_lru_cache, atomic_rewrite, check_run, importer, system_packages_info, ) from defence360agent.utils.fd_ops import open_dir_no_symlinks, safe_dir from defence360agent.model.wordpress import WPSite from defence360agent.wordpress.constants import WP_CLI_WRAPPER_PATH from defence360agent.wordpress.exception import PHPError CAGEFS_ENTER_PATH = "/usr/sbin/cagefs_enter_user" CAGEFS_CTL_PATH = "/usr/sbin/cagefsctl" MalwareHit = importer.get( module="imav.malwarelib.model", name="MalwareHit", default=None ) @cache def _queue_supervisor_cls(): return importer.get( module="imav.malwarelib.scan.queue_supervisor_sync", name="QueueSupervisorSync", default=None, ) @cache def _fetch_user_list_fn(): return importer.get( module="imav.malwarelib.utils.user_list", name="fetch_user_list", default=None, ) @cache def _sort_user_list_fn(): return importer.get( module="imav.malwarelib.utils.user_list", name="sort", default=None, ) _VALID_PRESETS = frozenset(("balanced", "strict", "monitor")) logger = logging.getLogger(__name__) def _validate_preset(value: object) -> str: """Coerce a config-read preset value to a canonical preset string. Returns "balanced" for anything outside _VALID_PRESETS — including None, non-strings, and hand-edited values like "extreme" or "BALANCED". The agent always writes lowercase canonical values, so a non-canonical read indicates either a manual edit or a future preset that this version doesn't recognise; "balanced" is the safe fallback in both cases. """ if isinstance(value, str) and value in _VALID_PRESETS: return value return "balanced" @async_lru_cache(ttl=60) async def get_domain_paths() -> dict[str, list[str]]: """ Get a mapping of docroots to their associated domains, with caching. """ hosting_panel = HostingPanel() panel_paths = await hosting_panel.get_domain_paths() docroot_map = defaultdict(list) for domain, docroots in panel_paths.items(): for docroot in docroots: docroot_map[docroot].append(domain) return docroot_map def wp_wrapper(php_path: str, docroot: str) -> list: """Get wp cli common command list""" return [str(WP_CLI_WRAPPER_PATH), php_path, docroot] @lru_cache(maxsize=1) def get_cagefs_enabled_users() -> set: """Get the list of users enabled for CageFS.""" if not os.path.isfile(CAGEFS_CTL_PATH) or not os.access( CAGEFS_CTL_PATH, os.X_OK ): return set() result = subprocess.run( [CAGEFS_CTL_PATH, "--list-enabled"], capture_output=True, text=True ) if result.returncode != 0: return set() lines = result.stdout.strip().split("\n") return set(lines[1:]) # Skip the first line which is a summary def clear_get_cagefs_enabled_users_cache(): """Clear the cache for get_cagefs_enabled_users.""" get_cagefs_enabled_users.cache_clear() def build_command_for_user(username: str, args: list) -> list: """Build the necessary command to run the given cmdline args with specified user.""" if username in get_cagefs_enabled_users(): if os.path.isfile(CAGEFS_ENTER_PATH) and os.access( CAGEFS_ENTER_PATH, os.X_OK ): return [ CAGEFS_ENTER_PATH, "--no-io-and-memory-limit", username, *args, ] return [ "su", "-s", "/bin/bash", username, "-c", shlex.join(args), ] async def get_domains_for_docroot( docroot: str, domain_to_exclude: str ) -> list[str]: """ Get all domains associated with a given document root, excluding one domain. It's panel-agnostic and uses a cached mapping. """ docroot_map = await get_domain_paths() all_domains = docroot_map.get(docroot, []) return [domain for domain in all_domains if domain != domain_to_exclude] async def get_php_binary_path(site: WPSite, username: str) -> Optional[str]: """Determine PHP binary path for the given WPSite.""" from clcommon.cpapi import ( get_domains_php_info, get_installed_php_versions, ) domains_php_info = get_domains_php_info() installed_php_versions = get_installed_php_versions() def find_php_binary_for_domain(domain: str) -> Optional[str]: domain_info = domains_php_info.get(domain) if not domain_info or domain_info.get("username") != username: return None php_display_version = domain_info.get("display_version") if not php_display_version: return None for php_version in installed_php_versions: if php_version.get("identifier") == php_display_version: return php_version.get("bin") return None # First, try with the main domain of the site. php_binary_path = find_php_binary_for_domain(site.domain) if php_binary_path: return php_binary_path # If not found, try with other domains for the site's docroot. domains = await get_domains_for_docroot( site.docroot, domain_to_exclude=site.domain ) for domain in domains: php_binary_path = find_php_binary_for_domain(domain) if php_binary_path: return php_binary_path raise PHPError( f"PHP binary was not identified for docroot: {site.docroot}, username:" f" {username}" ) def get_malware_history(username: str) -> list: """ Get malware history for the specified user. This is an equivalent of calling `imunify360-agent malware history list --user {username}`. Returns empty list if imav malware module is not available. """ if MalwareHit is None: logger.debug( "imav.malwarelib not available, returning empty malware history" ) return [] (max_count, hits) = MalwareHit.malicious_list(user=username) return hits async def get_last_scan(sink, username: str) -> dict: """ Get the last scan for the specified user. This is an equivalent of calling `imunify360-agent malware user list --user {username}`. Returns empty dict if imav malware module is not available. """ queue_supervisor_cls = _queue_supervisor_cls() fetch_user_list = _fetch_user_list_fn() sort_user_list = _sort_user_list_fn() if ( queue_supervisor_cls is None or fetch_user_list is None or sort_user_list is None ): logger.debug( "imav.malwarelib not available, returning empty last scan" ) return {} queue = queue_supervisor_cls(sink) _, users = await fetch_user_list( queue.get_scans_from_paths, match={username} ) if not users: return {} users = sort_user_list(users, "scan_date", desc=True) return users[0] def calculate_next_scan_timestamp(interval, hour, day_of_month, day_of_week): """ Calculate the next scan timestamp based on schedule configuration. Args: interval: Scan interval (DAY, WEEK, MONTH, or NONE) hour: Hour of day to run scan (0-23) day_of_month: Day of month to run scan (1-31) day_of_week: Day of week to run scan (0-6, where 0=Sunday) Returns: Timestamp of next scan, or None if interval is NONE """ today = datetime.utcnow() if interval == Interval.DAY: next_scan = today.replace( hour=hour, minute=0, second=0, microsecond=0, ) if today >= next_scan: next_scan += timedelta(days=1) return next_scan.timestamp() if interval == Interval.WEEK: # today.weekday() returns 0 for Monday, 6 for Sunday, but day_of_week uses 0 for Sunday, # 1 for Monday, ..., 6 for Saturday. So we need to adjust the calculation. days_ahead = (day_of_week - (today.weekday() + 1) % 7 + 7) % 7 if days_ahead == 0 and today.hour >= hour: days_ahead = 7 next_scan_date = today + timedelta(days=days_ahead) return next_scan_date.replace( hour=hour, minute=0, second=0, microsecond=0 ).timestamp() if interval == Interval.MONTH: from calendar import monthrange def find_next_suitable_month(year, month, days): """Find the next month that has at least given number of days.""" current_year, current_month = year, month # Always start with the next month when advancing current_month += 1 if current_month > 12: current_month = 1 current_year += 1 # Keep advancing months until we find one with enough days while True: days_in_month = monthrange(current_year, current_month)[1] if days <= days_in_month: return current_year, current_month current_month += 1 if current_month > 12: current_month = 1 current_year += 1 # Check if we need to advance to next month should_advance_month = ( # Today is after the scheduled day, scan already ran this month today.day > day_of_month # Today is the scheduled day and the hour is after the scheduled hour, scan already ran earlier today or (today.day == day_of_month and today.hour >= hour) # Current month doesn't have enough days, scan should run next suitable month or day_of_month > monthrange(today.year, today.month)[1] ) if should_advance_month: # Find the next month that can accommodate the configured day next_year, next_month = find_next_suitable_month( today.year, today.month, day_of_month ) next_scan_date = today.replace( day=day_of_month, # Use the actual configured day month=next_month, year=next_year, hour=hour, minute=0, second=0, microsecond=0, ) else: # Current month can accommodate the configured day next_scan_date = today.replace( day=day_of_month, hour=hour, minute=0, second=0, microsecond=0, ) return next_scan_date.timestamp() async def get_imunify_package_versions() -> dict[str, str | None]: """Fetch installed versions of Imunify packages. Returns a dict mapping package name to version string, with None for packages that are not installed. Intended to be called once per sync cycle (not per site). """ return await system_packages_info(IMUNIFY_PACKAGE_NAMES) def prepare_scan_data( last_scan_time: float, next_scan_time: float, username: str, site: WPSite, malware_by_site: dict, versions: dict[str, str | None] | None = None, ) -> dict: """ Prepare scan data JSON for a WordPress site. Args: last_scan_time: Timestamp of the last scan next_scan_time: Timestamp of the next scheduled scan username: Username of the site owner site: WordPress site object malware_by_site: Dictionary mapping site docroots to their malware hits versions: Optional dict mapping Imunify package names to version strings (None for uninstalled packages). When provided, included in the output as a ``versions`` key. Returns: dict: JSON data ready to be written to scan_data.php. The response includes: - lastScanTimestamp: Timestamp of the last scan - nextScanTimestamp: Timestamp of the next scheduled scan - username: Username of the site owner - malware: List of malware hits for the site - config: Configuration items for the site - license: License information including status and eligibility for Imunify patch - versions: (optional) Installed Imunify package versions """ # Define the config sections and options needed config_sections = [ ("MALWARE_SCANNING", "enable_scan_cpanel"), ("MALWARE_SCANNING", "default_action"), ("PROACTIVE_DEFENCE", "blamer"), ] # Build the config items config_items = {} for section, option in config_sections: if section not in config_items: config_items[section] = {} try: value, _ = choose_value_from_config( section, option, username=username, ) except KeyError: value = None config_items[section][option] = value result = { "lastScanTimestamp": last_scan_time, "nextScanTimestamp": next_scan_time, "username": username, "malware": malware_by_site.get(site.docroot, []), "config": config_items, "license": LicenseCLN.license_info(), } if versions is not None: result["versions"] = versions return result def _prepare_ai_bot_settings(username: str) -> dict: """The WP-plugin-facing WORDPRESS toggles, without the license read. Split from prepare_plugin_config so callers that only need the admin toggles (e.g. stats collection) don't trigger a license-token read. """ try: ai_bot_protection, _ = choose_value_from_config( "WORDPRESS", "ai_bot_protection", username=username, ) except KeyError: ai_bot_protection = False try: preset, _ = choose_value_from_config( "WORDPRESS", "ai_bot_protection_preset", username=username, ) except KeyError: preset = "balanced" return { "ai_bot_protection": bool(ai_bot_protection), "preset": _validate_preset(preset), } def prepare_plugin_config(username: str) -> dict: """ Prepare the plugin_config.php payload. Dedicated channel for WP-plugin-facing configuration that the mu-plugin reads on the request hot path. Kept separate from scan_data.php so that a config toggle doesn't force rewriting the (potentially large) malware list, and so the mu-plugin loads only the data it actually needs per request. Forward compatibility: the plugin ships with the agent, so their versions are in lockstep. Any forward-compat gating lives here on the writer side — the agent simply omits a field it doesn't know about, and the plugin treats missing fields as "unset, use safe default". No per-field version stamp is needed in the file itself. Args: username: Owner of the WP site. Returns: Dict ready to be encoded as PHP via format_php_with_embedded_json: - ai_bot_protection: bool — admin WORDPRESS.ai_bot_protection - preset: str — admin WORDPRESS.ai_bot_protection_preset, normalised via _validate_preset to one of "balanced"/"strict"/"monitor". Falls back to "balanced" when the schema lacks the key (older agent) or the configured value is non-canonical (manual edit, future preset). - license_type: str | None — server license edition (imunify360 / imunify360Trial / imunifyAV / imunifyAVPlus), or None when it can't be determined; the plugin treats None as "do not gate". """ settings = _prepare_ai_bot_settings(username) settings["license_type"] = LicenseCLN.get_license_type() return settings def write_plugin_data_file_atomically( file_path, content: str, uid: int, gid: int, *, dir_fd: int | None = None ) -> None: """Write a plugin data file atomically. When *dir_fd* is supplied by the caller (e.g. from ensure_site_data_directory) it is used directly; otherwise the parent directory is opened with symlink protection. """ permissions = 0o440 if HostingPanel().NAME == Plesk.NAME else 0o400 if dir_fd is not None: atomic_rewrite( file_path, content, backup=False, uid=uid, gid=gid, permissions=permissions, dir_fd=dir_fd, ) return with safe_dir(file_path.parent) as owned_dir_fd: atomic_rewrite( file_path, content, backup=False, uid=uid, gid=gid, permissions=permissions, dir_fd=owned_dir_fd, ) def _escape_json_for_php_single_quoted_string(json_str: str) -> str: """ Escape a JSON string for embedding inside a PHP single-quoted string. PHP single-quoted strings only recognise two escape sequences: ``\\\\`` (literal backslash) and ``\\'`` (literal single quote). All other backslash sequences are kept verbatim. That means we must double every ``\\`` *before* we escape ``'``, otherwise PHP will consume JSON backslashes (e.g. ``\\\\s`` in JSON becomes ``\\s`` after PHP parsing, which is not a valid JSON escape). """ return json_str.replace("\\", "\\\\").replace("'", "\\'") def _unescape_php_single_quoted_json(escaped: str) -> str: """ Reverse the escaping applied by :func:`_escape_json_for_php_single_quoted_string`. """ return escaped.replace("\\'", "'").replace("\\\\", "\\") def format_php_with_embedded_json(data: dict) -> str: """ Format a dictionary as a PHP file that returns JSON-decoded data. This creates a WordPress-safe PHP file that: 1. Checks if it's being included from WordPress (WPINC defined) 2. Returns the data as a decoded JSON string Args: data: Dictionary to embed in the PHP file Returns: Formatted PHP file content as a string """ return ( " dict: """ Parse a PHP file generated by format_php_with_embedded_json. Extracts and returns the embedded JSON data. Args: content: PHP file content string Returns: Parsed JSON data as a dict Raises: ValueError: If the JSON data cannot be found or parsed """ marker = "json_decode( '" start = content.find(marker) if start == -1: raise ValueError("No embedded JSON found in PHP content") start += len(marker) end = content.find("', true )", start) if end == -1: raise ValueError("Malformed embedded JSON in PHP content") json_str = _unescape_php_single_quoted_json(content[start:end]) return json.loads(json_str) def ensure_directory_listing_protection( data_dir: Path, uid: int, gid: int, *, dir_fd: int ) -> None: """ Ensure directory listing protection files exist in the data directory. Creates .htaccess, index.php, and index.html files to prevent directory listing. All writes use the caller-supplied *dir_fd* so that no path-based symlink check is required. atomic_rewrite skips the write when the file already contains the expected content, preserving idempotency. """ protection_files = { ".htaccess": "DirectoryIndex index.php index.html\ndeny from all\n", "index.php": "\n", } for filename, content in protection_files.items(): file_path = data_dir / filename write_plugin_data_file_atomically( file_path, content, uid=uid, gid=gid, dir_fd=dir_fd ) async def ensure_site_data_directory( site: WPSite, user_info: pwd.struct_passwd ) -> Path: """Ensure the site's data directory exists with correct permissions. The directory is opened with symlink protection after creation (or if it already exists) to obtain a stable file descriptor. All subsequent operations use that descriptor. Args: site: WordPress site user_info: User information from pwd Returns: Path to data directory Raises: Exception: If the data directory is a symlink or cannot be created """ from defence360agent.wordpress import cli data_dir = await cli.get_data_dir(site) newly_created = False try: dir_fd = open_dir_no_symlinks(data_dir) except FileNotFoundError: # Directory does not exist yet — create it as the site user so that # it is owned by the user (not root), then re-open with O_NOFOLLOW. command = build_command_for_user( user_info.pw_name, ["mkdir", "-p", str(data_dir)], ) await check_run(command) try: dir_fd = open_dir_no_symlinks(data_dir) except OSError as exc: if exc.errno in (errno.ELOOP, errno.ENOTDIR): raise Exception( f"Data directory {data_dir} is a symlink, skipping." ) from exc raise Exception( f"Failed to open data directory {data_dir}: {exc}" ) from exc newly_created = True except OSError as exc: if exc.errno in (errno.ELOOP, errno.ENOTDIR): raise Exception( f"Data directory {data_dir} is a symlink, skipping." ) from exc raise try: if newly_created: os.chmod(dir_fd, 0o750) ensure_directory_listing_protection( data_dir, uid=site.uid, gid=user_info.pw_gid, dir_fd=dir_fd ) finally: os.close(dir_fd) return data_dir defence360agent/wordpress/wp_rules.py0000644000000000000000000000670400000000000015017 0ustar """WordPress rules file management. This module provides utilities for loading and parsing wp-rules.yaml from the files.imunify360.com index system. Available for both AV and IM360 modes. """ import logging import yaml import zipfile from pathlib import Path from defence360agent.files import Index logger = logging.getLogger(__name__) # WordPress rules file names within the index WP_RULES_ZIP_FILENAME = "wp-rules.zip" WP_RULES_VERSION_FILENAME = "VERSION" def find_file_in_index(index: Index, filename: str) -> Path | None: """ Find a file path from the index by filename. Args: index: files.Index object filename: Name of the file to find (e.g., WP_RULES_ZIP_FILENAME) Returns: Path to the file or None if not found """ for item in index.items(): if item["name"] == filename: file_path = Path(index.localfilepath(item["url"])) if file_path.exists(): return file_path logger.error("%s not found in %s", filename, index.files_path(index.type)) return None def extract_wp_rules_yaml(zip_path: Path) -> dict | None: """ Extract and parse wp-rules.yaml from the zip file. Args: zip_path: Path to wp-rules.zip file Returns: Parsed YAML data as dict or None if extraction/parsing fails """ try: with zipfile.ZipFile(zip_path, "r") as zip_file: with zip_file.open("wp-rules.yaml") as yaml_file: rules_data = yaml.safe_load(yaml_file) except (zipfile.BadZipFile, KeyError, yaml.YAMLError) as e: logger.error("Failed to extract or parse wp-rules.yaml: %s", e) return None if not isinstance(rules_data, dict): logger.error("Invalid wp-rules.yaml format: %s", rules_data) return None return rules_data def get_wp_rules_data(index: Index) -> dict | None: """ Retrieve the latest WordPress rules and return them as a dictionary. Args: index: The files.Index object used to locate the wp-rules.zip file. Returns: The parsed wp-rules data as a dictionary. If the wp-rules archive or data cannot be found or parsed, returns None. Note: This function returns the raw rules data. Callers that need to modify rules based on product mode (e.g., ANTIVIRUS_MODE) should do so after calling this function. """ # Find wp-rules.zip file zip_path = find_file_in_index(index, WP_RULES_ZIP_FILENAME) if not zip_path: return None # Extract and parse wp-rules.yaml rules_data = extract_wp_rules_yaml(zip_path) if not rules_data: return None logger.info("Successfully parsed wp-rules.yaml") return rules_data def get_wp_ruleset_version(index: Index) -> str: """ Retrieve the WordPress ruleset version string from the VERSION file. Args: index: The files.Index object used to locate the VERSION file. Returns: The version string from the VERSION file. If the VERSION file cannot be found or read, returns "NA". """ # Find VERSION file version_path = find_file_in_index(index, WP_RULES_VERSION_FILENAME) if not version_path: return "NA" try: version_string = version_path.read_text().strip() logger.info("Successfully read wp-rules version: %s", version_string) return version_string except Exception as e: logger.error("Failed to read VERSION file: %s", e) return "NA" imunify_core-8.12.1-py3.11.egg-info/0000755000000000000000000000000000000000000013545 5ustar imunify_core-8.12.1-py3.11.egg-info/PKG-INFO0000644000000000000000000000027100000000000014642 0ustar Metadata-Version: 2.1 Name: imunify-core Version: 8.12.1 Summary: ImunifyCore Home-page: http://www.imunify360.com Author: Cloud Linux Zug GmbH Author-email: imunify-dev@imunify360.com imunify_core-8.12.1-py3.11.egg-info/SOURCES.txt0000644000000000000000000006422500000000000015442 0ustar MANIFEST.in README.md pyproject.toml setup-core.py setup.cfg setup.py defence360agent/__init__.py defence360agent/__main__.py defence360agent/_version.py defence360agent/defence360.py defence360agent/migrate.py defence360agent/router.py defence360agent/run.py defence360agent/sentry.py defence360agent/api/__init__.py defence360agent/api/health.py defence360agent/api/inactivity.py defence360agent/api/integration_conf.py defence360agent/api/jwt_issuer.py defence360agent/api/newsfeed.py defence360agent/api/pam_auth.py defence360agent/api/server/__init__.py defence360agent/api/server/analyst_cleanup.py defence360agent/api/server/cleanup_revert.py defence360agent/api/server/events.py defence360agent/api/server/reputation.py defence360agent/api/server/send_message.py defence360agent/application/__init__.py defence360agent/application/determine_hosting_panel.py defence360agent/application/settings.py defence360agent/application/tags.py defence360agent/contracts/__init__.py defence360agent/contracts/config.py defence360agent/contracts/config_provider.py defence360agent/contracts/eula.py defence360agent/contracts/hook_events.py defence360agent/contracts/hooks.py defence360agent/contracts/license.py defence360agent/contracts/messages.py defence360agent/contracts/myimunify_id.py defence360agent/contracts/permissions.py defence360agent/contracts/plugins.py defence360agent/contracts/sentry.py defence360agent/feature_management/__init__.py defence360agent/feature_management/checkers.py defence360agent/feature_management/constants.py defence360agent/feature_management/control.py defence360agent/feature_management/exceptions.py defence360agent/feature_management/hooks.py defence360agent/feature_management/lookup.py defence360agent/feature_management/model.py defence360agent/feature_management/utils.py defence360agent/feature_management/plugins/__init__.py defence360agent/feature_management/plugins/native.py defence360agent/feature_management/plugins/proactive_log_migration.py defence360agent/feature_management/rpc/__init__.py defence360agent/feature_management/rpc/endpoints/__init__.py defence360agent/feature_management/rpc/endpoints/native.py defence360agent/feature_management/rpc/endpoints/show.py defence360agent/feature_management/rpc/endpoints/update.py defence360agent/feature_management/rpc/endpoints/utils.py defence360agent/feature_management/rpc/schema/native.pickle defence360agent/feature_management/rpc/schema/native.yaml defence360agent/feature_management/rpc/schema/show.pickle defence360agent/feature_management/rpc/schema/show.yaml defence360agent/feature_management/rpc/schema/update.pickle defence360agent/feature_management/rpc/schema/update.yaml defence360agent/files/__init__.py defence360agent/files/hooks.py defence360agent/hooks/__init__.py defence360agent/hooks/execute.py defence360agent/hooks/native.py defence360agent/internals/__init__.py defence360agent/internals/auth_protocol.py defence360agent/internals/cln.py defence360agent/internals/deadlock_detecting_lock.py defence360agent/internals/delivery_ack.py defence360agent/internals/feature_flags.py defence360agent/internals/geo.py defence360agent/internals/global_scope.py defence360agent/internals/iaid.py defence360agent/internals/lazy_load.py defence360agent/internals/logger.py defence360agent/internals/logging_protocol.py defence360agent/internals/message_status_publisher.py defence360agent/internals/persistent_message.py defence360agent/internals/the_sink.py defence360agent/migrations/001_initial.py defence360agent/migrations/002_infected_domain_list.py defence360agent/migrations/003_import_from_list.py defence360agent/migrations/004_add_username_to_infected_domain_list.py defence360agent/migrations/005_timeout_in_iplist.py defence360agent/migrations/006_comment_in_plist.py defence360agent/migrations/007_add_country_code_fields.py defence360agent/migrations/008_fill_countries.py defence360agent/migrations/009_drop_blocklist_history.py defence360agent/migrations/010_drop_country_entities.py defence360agent/migrations/011_create_new_country_entities.py defence360agent/migrations/012_fill_countries_and_subnets.py defence360agent/migrations/013_add_indexes_to_iplist.py defence360agent/migrations/014_add_malware_hits.py defence360agent/migrations/015_add_iplist_expiration_index.py defence360agent/migrations/016_fix_autowhitelist_expiration.py defence360agent/migrations/017_remove_sensor_prefix.py defence360agent/migrations/018_license_info.py defence360agent/migrations/019_purge_old_configs.py defence360agent/migrations/020_malware_scan_types.py defence360agent/migrations/021_add_testing_repo.py defence360agent/migrations/022_mod_security_vendors_migrations.py defence360agent/migrations/023_add_default_rule_in_modsec_custom_conf.py defence360agent/migrations/024_ignore_from_graylist.py defence360agent/migrations/025_malware_config_realtime.py defence360agent/migrations/026_remove_old_temporary_file.py defence360agent/migrations/027_disable_comdo_fp_rules.py defence360agent/migrations/028_set_permanent_ttl_for_blacklist.py defence360agent/migrations/029_custom_quarantine.py defence360agent/migrations/030_rename_max_incident_repetition.py defence360agent/migrations/031_add_mode_field.py defence360agent/migrations/031_modsec_config_for_plesk_include.py defence360agent/migrations/032_chmod_quarantine.py defence360agent/migrations/033_disable_cphulk.py defence360agent/migrations/034_hits_extras.py defence360agent/migrations/035_add_dos_expiration_field.py defence360agent/migrations/036_add_block_port.py defence360agent/migrations/037_disabled_rules.py defence360agent/migrations/038_disabled_rules_import.py defence360agent/migrations/039_fix_malware_hits.py defence360agent/migrations/040_ignore_mod_sec_rule_214920.py defence360agent/migrations/041_fix_invalid_ignore_filed.py defence360agent/migrations/042_rebuildinstalledssldb.py defence360agent/migrations/043_disable_dos_scan_by_default.py defence360agent/migrations/044_ignore_virtfs_on_cpanel.py defence360agent/migrations/045_ignore_vdserver_dir_in_csf.py defence360agent/migrations/046_foreign_key_fix.py defence360agent/migrations/047_license_in_file.py defence360agent/migrations/048_malware_hits_vendor_field.py defence360agent/migrations/049_add_auto_added_field_to_iplist.py defence360agent/migrations/050_fill_auto_whitelisted.py defence360agent/migrations/051_cleanup_vd_license.py defence360agent/migrations/052_whitelisted_crawlers.py defence360agent/migrations/053_populate_whitelisted_crawlers.py defence360agent/migrations/054_add_malicious_and_added_date_fileds.py defence360agent/migrations/055_migrate_move_to_quar_option.py defence360agent/migrations/056_populate_malicious_with_quarantined.py defence360agent/migrations/057_filename_is_blob.py defence360agent/migrations/058_convert_license_last_attempt.py defence360agent/migrations/059_scans_error_field.py defence360agent/migrations/061_migrate_backup_system_conf.py defence360agent/migrations/062_drop_malware_extra_data.py defence360agent/migrations/062_fix_null_expiration.py defence360agent/migrations/063_fix_graylist_doslist_expiration_discrepancy.py defence360agent/migrations/064_chmod_i360deploy_log.py defence360agent/migrations/065_remove_capture_csf_lock_from_config.py defence360agent/migrations/066_eula_table.py defence360agent/migrations/067_drop_fields_from_modsec_conf.py defence360agent/migrations/068_remove_rules_check_interval_from_config.py defence360agent/migrations/069_incidents_domain_field.py defence360agent/migrations/070_modsec_incident_names.py defence360agent/migrations/071_malware_hits_hash_size_fields.py defence360agent/migrations/072_add_malware_history_table.py defence360agent/migrations/072_captcha_stat.py defence360agent/migrations/072_extend_last_synclist.py defence360agent/migrations/073_drop_dos_expiration.py defence360agent/migrations/074_ip_as_int.py defence360agent/migrations/075_ips_as_int.py defence360agent/migrations/076_hash_model.py defence360agent/migrations/077_alter_malware_scan.py defence360agent/migrations/078_fix_signatures_permissions.py defence360agent/migrations/079_add_uid_gid_fields.py defence360agent/migrations/080_populate_uid_gid_size_hash_fields.py defence360agent/migrations/081_fix_clamscan_broken_symlink.py defence360agent/migrations/082_add_cl_on_premise_backup_option.py defence360agent/migrations/082_add_manual_flag.py defence360agent/migrations/083_drop_no_captcha_field.py defence360agent/migrations/084_country_subnets_fields.py defence360agent/migrations/085_country_subnets_fields.py defence360agent/migrations/086_ignored_by_port_fields.py defence360agent/migrations/087_ignored_by_port_fields.py defence360agent/migrations/088_add_malware_i360_clamd_scan_option.py defence360agent/migrations/089_proactive_tables.py defence360agent/migrations/090_safe_user_config.py defence360agent/migrations/091_compress_old_logs.py defence360agent/migrations/092_ignore_proc_sys_dirs.py defence360agent/migrations/092_remove_old_disabled_rules.py defence360agent/migrations/093_make_quarantined_files_immutable.py defence360agent/migrations/094_ignore_cagefs_proc.py defence360agent/migrations/095_add_total_malicious_field.py defence360agent/migrations/096_populate_total_malicious_field.py defence360agent/migrations/097_remove_uid_and_gid.py defence360agent/migrations/098_remote_proxy_tables.py defence360agent/migrations/099_remove_old_disabled_rules.py defence360agent/migrations/100_remove_captcha_ports_from_csf.py defence360agent/migrations/101_remove_unneeded_acronis_ports_from_csf.py defence360agent/migrations/102_proactive_ignore_list.py defence360agent/migrations/102_replace_comodo.py defence360agent/migrations/103_remove_vd_license.py defence360agent/migrations/104_add_feature_management_permissions.py defence360agent/migrations/105_populate_default_feature_management_permissions.py defence360agent/migrations/106_add_malware_cleanup_in_config.py defence360agent/migrations/106_malware_hit_status_field_add.py defence360agent/migrations/107_add_bruteforce_rule_33339.py defence360agent/migrations/107_malware_hit_status_field_populate.py defence360agent/migrations/108_feature_management_cleanup_add.py defence360agent/migrations/108_validate_config.py defence360agent/migrations/109_dos_detector.py defence360agent/migrations/110_ignore_list_ip_as_int.py defence360agent/migrations/111_ignore_list_ip_as_int.py defence360agent/migrations/112_hardened_php.py defence360agent/migrations/113_move_quarantined_files.py defence360agent/migrations/114_disable_auto-quarantine.py defence360agent/migrations/115_feature_management_fields.py defence360agent/migrations/116_feature_management_fields.py defence360agent/migrations/117_remove_incorrect_fields.py defence360agent/migrations/118_add_malware_user_infected.py defence360agent/migrations/118_remove_country_subnets.py defence360agent/migrations/119_populate_malware_user_infected.py defence360agent/migrations/120_scheduled_scan.py defence360agent/migrations/121_drop_captcha_stat.py defence360agent/migrations/122_cagefs_unmount.py defence360agent/migrations/123_add_last_user_scan.py defence360agent/migrations/123_disable_scheduled_scan.py defence360agent/migrations/123_rename_plesk_vendor.py defence360agent/migrations/124_add_hook_management_functionality.py defence360agent/migrations/124_add_infected_domains_vendor.py defence360agent/migrations/125_rescan_scan_type.py defence360agent/migrations/126_add_malware_scan_modified_files_option.py defence360agent/migrations/126_move_malware_hits_list.py defence360agent/migrations/127_remove_malware_hit_mode.py defence360agent/migrations/128_move_cleanup_storage_files.py defence360agent/migrations/129_fixed_cagefs_unmount.py defence360agent/migrations/130_add_messages_to_send.py defence360agent/migrations/131_incident_timestamp_index.py defence360agent/migrations/132_add_timestamp_field.py defence360agent/migrations/133_add_scope_field_to_iplist.py defence360agent/migrations/134_change_default_of_intensity_ram.py defence360agent/migrations/135_export_proactive.py defence360agent/migrations/135_make_completed_nullable.py defence360agent/migrations/136_drop_proactive.py defence360agent/migrations/137_swap_initiator_and_cause.py defence360agent/migrations/138_move_rapid_scan_dir.py defence360agent/migrations/139_generic_modsec_config.py defence360agent/migrations/140_cast_malware_hit_orig_file_as_blob.py defence360agent/migrations/141_drop_last_user_scans.py defence360agent/migrations/143_malware_hit_cascade_delete.py defence360agent/migrations/144_remove_clamav_config_options.py defence360agent/migrations/144_remove_hash_table.py defence360agent/migrations/145_move_quarantine.py defence360agent/migrations/146_malware_user_infected_cascade_delete.py defence360agent/migrations/147_remove_vendor_field.py defence360agent/migrations/147_user_scan_type.py defence360agent/migrations/148_reconstruct_pickled_scan_queue.py defence360agent/migrations/148_remove_malware_user_infected.py defence360agent/migrations/149_add_captcha_passed_field_to_iplist.py defence360agent/migrations/149_make_config_inactive.py defence360agent/migrations/150_update_captcha_passed_field_for_iplist_entries.py defence360agent/migrations/151_change_constraint_for_iplist.py defence360agent/migrations/152_add_listname_to_primary_key.py defence360agent/migrations/153_migrate_config_default_action.py defence360agent/migrations/153_update_incident_name.py defence360agent/migrations/154_migrate_config_user_override_malware_actions.py defence360agent/migrations/155_migrate_config_user_override_proactive_defense.py defence360agent/migrations/156_remove_default_values_from_config.py defence360agent/migrations/157_move_i360_modsec_disable_conf.py defence360agent/migrations/158_move_i360_modsec_disable_conf_symlink.py defence360agent/migrations/159_remove_defaults_from_local_config.py defence360agent/migrations/160_remove_quarantine.py defence360agent/migrations/160_unmount_sigs_v1.py defence360agent/migrations/161_remove_ea4_main_local_conf.py defence360agent/migrations/162_add_resource_type.py defence360agent/migrations/163_drop_malware_scanned_stat.py defence360agent/migrations/164_add_resource_type_to_ignore.py defence360agent/migrations/165_add_db_fields_to_malware_history.py defence360agent/migrations/166_add_id_field_to_malware_ignore_path.py defence360agent/migrations/167_remote_iplist.py defence360agent/migrations/168_add_icontact_throttle.py defence360agent/migrations/169_add_record_to_throttle_scan_not_schedule_events.py defence360agent/migrations/170_add_db_fields_to_malware_history.py defence360agent/migrations/180_move_captcha_configs.py defence360agent/migrations/182_remove_constraints_from_icontact_throttle.py defence360agent/migrations/183_add_user_field_to_malware_scans.py defence360agent/migrations/184_create_a_table_for_secure_site_permissions.py defence360agent/migrations/185_delete_all_secure_site_id.py defence360agent/migrations/186_add_user_field_to_icontact_throttle.py defence360agent/migrations/187_fix_scan_unserialization.py defence360agent/migrations/188_add_protection_status_field_myimunify.py defence360agent/migrations/189_add_messages_to_send_nr.py defence360agent/migrations/190_add_analyst_cleanup_request_table.py defence360agent/migrations/191_create_wordpress_incident_table.py defence360agent/migrations/192_add_wordpress_incident_unique_index.py defence360agent/migrations/193_remove_sent_to_server_from_wordpress_incident.py defence360agent/migrations/194_add_wp_disabled_rules.py defence360agent/migrations/194_create_nonprivileged_config.py defence360agent/migrations/195_create_wordpress_site.py defence360agent/migrations/196_add_disabled_rules_sync_ts.py defence360agent/migrations/197_add_wordpress_site_manually_deleted_at.py defence360agent/migrations/198_add_wordpress_site_version.py defence360agent/migrations/199_proactive_log_permission.py defence360agent/migrations/200_seed_per_user_waf_enabled.py defence360agent/migrations/201_rerender_nonprivileged_config.py defence360agent/migrations/202_add_wordpress_incident_bucket.py defence360agent/migrations/203_add_wordpress_incident_unsent_retries.py defence360agent/migrations/__init__.py defence360agent/migrations/conf.py defence360agent/model/__init__.py defence360agent/model/analyst_cleanup.py defence360agent/model/event_hook.py defence360agent/model/icontact.py defence360agent/model/infected_domain.py defence360agent/model/instance.py defence360agent/model/messages_to_send.py defence360agent/model/simplification.py defence360agent/model/tls_check.py defence360agent/model/wordpress.py defence360agent/model/wordpress_incident.py defence360agent/model/wp_disabled_rule.py defence360agent/mr_proper/__init__.py defence360agent/myimunify/__init__.py defence360agent/myimunify/billing.py defence360agent/myimunify/constants.py defence360agent/myimunify/model.py defence360agent/myimunify/advice/__init__.py defence360agent/myimunify/advice/advice_manager.py defence360agent/myimunify/advice/dataclass.py defence360agent/myimunify/advice/hosting_smart_advice_api.py defence360agent/plugins/__init__.py defence360agent/plugins/accumulate.py defence360agent/plugins/analyst_cleanup_update.py defence360agent/plugins/backup_info_sender.py defence360agent/plugins/cagefs.py defence360agent/plugins/checkpoint.py defence360agent/plugins/client.py defence360agent/plugins/config_merger.py defence360agent/plugins/config_watcher.py defence360agent/plugins/event_hook_executor.py defence360agent/plugins/event_monitor.py defence360agent/plugins/event_monitor_message_processor.py defence360agent/plugins/feature_flags.py defence360agent/plugins/files_recurring_update.py defence360agent/plugins/icontact_sender.py defence360agent/plugins/idle_time_out.py defence360agent/plugins/lve_utils_install.py defence360agent/plugins/myimunify.py defence360agent/plugins/ping.py defence360agent/plugins/send_domain_list.py defence360agent/plugins/send_server_config.py defence360agent/plugins/service_manager.py defence360agent/plugins/wordpress.py defence360agent/rpc_tools/__init__.py defence360agent/rpc_tools/exceptions.py defence360agent/rpc_tools/lookup.py defence360agent/rpc_tools/middleware.py defence360agent/rpc_tools/utils.py defence360agent/rpc_tools/validate.py defence360agent/simple_rpc/__init__.py defence360agent/simple_rpc/advisor.py defence360agent/simple_rpc/analyst_cleanup.py defence360agent/simple_rpc/endpoints.py defence360agent/simple_rpc/hooks.py defence360agent/simple_rpc/hosting_panel.py defence360agent/simple_rpc/myimunify.py defence360agent/simple_rpc/permissions.py defence360agent/simple_rpc/plesk_stats.py defence360agent/simple_rpc/reputation_management.py defence360agent/simple_rpc/schema.py defence360agent/simple_rpc/wordpress_security_plugin.py defence360agent/simple_rpc/wp_disabled_rules.py defence360agent/simple_rpc/wp_waf_bulk.py defence360agent/simple_rpc/schema/advisor.pickle defence360agent/simple_rpc/schema/advisor.yaml defence360agent/simple_rpc/schema/analyst-cleanup.pickle defence360agent/simple_rpc/schema/analyst-cleanup.yaml defence360agent/simple_rpc/schema/auth-cloud.pickle defence360agent/simple_rpc/schema/auth-cloud.yaml defence360agent/simple_rpc/schema/billing.pickle defence360agent/simple_rpc/schema/billing.yaml defence360agent/simple_rpc/schema/checkdb.pickle defence360agent/simple_rpc/schema/checkdb.yaml defence360agent/simple_rpc/schema/config.pickle defence360agent/simple_rpc/schema/config.yaml defence360agent/simple_rpc/schema/conflicts.pickle defence360agent/simple_rpc/schema/conflicts.yaml defence360agent/simple_rpc/schema/doctor.pickle defence360agent/simple_rpc/schema/doctor.yaml defence360agent/simple_rpc/schema/eula.pickle defence360agent/simple_rpc/schema/eula.yaml defence360agent/simple_rpc/schema/files.pickle defence360agent/simple_rpc/schema/files.yaml defence360agent/simple_rpc/schema/get-news.pickle defence360agent/simple_rpc/schema/get-news.yaml defence360agent/simple_rpc/schema/google-safe-engine.pickle defence360agent/simple_rpc/schema/google-safe-engine.yaml defence360agent/simple_rpc/schema/hook.pickle defence360agent/simple_rpc/schema/hook.yaml defence360agent/simple_rpc/schema/hooks.pickle defence360agent/simple_rpc/schema/hooks.yaml defence360agent/simple_rpc/schema/hosting-panel.pickle defence360agent/simple_rpc/schema/hosting-panel.yaml defence360agent/simple_rpc/schema/login.pickle defence360agent/simple_rpc/schema/login.yaml defence360agent/simple_rpc/schema/package-versions.pickle defence360agent/simple_rpc/schema/package-versions.yaml defence360agent/simple_rpc/schema/permissions.pickle defence360agent/simple_rpc/schema/permissions.yaml defence360agent/simple_rpc/schema/plesk-stats.pickle defence360agent/simple_rpc/schema/plesk-stats.yaml defence360agent/simple_rpc/schema/registration.pickle defence360agent/simple_rpc/schema/registration.yaml defence360agent/simple_rpc/schema/support.pickle defence360agent/simple_rpc/schema/support.yaml defence360agent/simple_rpc/schema/version.pickle defence360agent/simple_rpc/schema/version.yaml defence360agent/simple_rpc/schema/wordpress.pickle defence360agent/simple_rpc/schema/wordpress.yaml defence360agent/simple_rpc/schema/wp-disabled-rules.pickle defence360agent/simple_rpc/schema/wp-disabled-rules.yaml defence360agent/simple_rpc/schema/wp-waf.pickle defence360agent/simple_rpc/schema/wp-waf.yaml defence360agent/simple_rpc/schema_responses/AnalystCleanupAllowedResponse.json defence360agent/simple_rpc/schema_responses/AnalystCleanupGetRequestsResponse.json defence360agent/simple_rpc/schema_responses/AnalystCleanupRequestResponse.json defence360agent/simple_rpc/schema_responses/ConfigAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementDefaultsAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementEditAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementGetAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementListAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementNativeStatusAgentResponse.json defence360agent/simple_rpc/schema_responses/FeaturesManagementShowAgentResponse.json defence360agent/simple_rpc/schema_responses/GetNewsAgentResponse.json defence360agent/simple_rpc/schema_responses/GetPackageVersionsAgentResponse.json defence360agent/simple_rpc/schema_responses/NoItemsAndEulaAgentResponse.json defence360agent/simple_rpc/schema_responses/NotificationConfigAgentResponse.json defence360agent/simple_rpc/schema_responses/NullAgentResponse.json defence360agent/simple_rpc/schema_responses/README.md defence360agent/simple_rpc/schema_responses/ReputationAgentResponse.json defence360agent/simple_rpc/schema_responses/TokenAgentResponse.json defence360agent/simple_rpc/schema_responses/WhmcsUpdateResponse.json defence360agent/simple_rpc/schema_responses/WordpressDomainsResponse.json defence360agent/simple_rpc/schema_responses/WordpressIncidentsListAgentResponse.json defence360agent/subsys/__init__.py defence360agent/subsys/ainotify.py defence360agent/subsys/backup_systems.py defence360agent/subsys/clcagefs.py defence360agent/subsys/notifier.py defence360agent/subsys/persistent_state.py defence360agent/subsys/svcctl.py defence360agent/subsys/sysctl.py defence360agent/subsys/systemd_notifier.py defence360agent/subsys/web_server.py defence360agent/subsys/features/__init__.py defence360agent/subsys/features/abstract_feature.py defence360agent/subsys/features/kernel_care.py defence360agent/subsys/panels/__init__.py defence360agent/subsys/panels/base.py defence360agent/subsys/panels/hosting_panel.py defence360agent/subsys/panels/cpanel/__init__.py defence360agent/subsys/panels/cpanel/packages.py defence360agent/subsys/panels/cpanel/panel.py defence360agent/subsys/panels/cpanel/whm.py defence360agent/subsys/panels/directadmin/__init__.py defence360agent/subsys/panels/directadmin/config.py defence360agent/subsys/panels/directadmin/panel.py defence360agent/subsys/panels/generic/__init__.py defence360agent/subsys/panels/generic/panel.py defence360agent/subsys/panels/generic/users_script_schemas/schema-admins.yaml defence360agent/subsys/panels/generic/users_script_schemas/schema-domains.yaml defence360agent/subsys/panels/generic/users_script_schemas/schema-metadata.yaml defence360agent/subsys/panels/generic/users_script_schemas/schema-panel_info.yaml defence360agent/subsys/panels/generic/users_script_schemas/schema-users.yaml defence360agent/subsys/panels/no_cp/__init__.py defence360agent/subsys/panels/no_cp/panel.py defence360agent/subsys/panels/plesk/__init__.py defence360agent/subsys/panels/plesk/api.py defence360agent/subsys/panels/plesk/panel.py defence360agent/subsys/panels/plesk/upgrade_urls.py defence360agent/subsys/panels/plesk/utils.py defence360agent/utils/__init__.py defence360agent/utils/_shutil.py defence360agent/utils/antivirus_mode.py defence360agent/utils/async_utils.py defence360agent/utils/benchmark.py defence360agent/utils/buffer.py defence360agent/utils/check_db.py defence360agent/utils/check_lock.py defence360agent/utils/cli.py defence360agent/utils/common.py defence360agent/utils/completions.py defence360agent/utils/config.py defence360agent/utils/cronjob.py defence360agent/utils/doctor.py defence360agent/utils/fd_ops.py defence360agent/utils/hyperscan.py defence360agent/utils/importer.py defence360agent/utils/ipecho.py defence360agent/utils/json.py defence360agent/utils/kwconfig.py defence360agent/utils/net.py defence360agent/utils/net_transport.py defence360agent/utils/parsers.py defence360agent/utils/resource_limits.py defence360agent/utils/safe_fileops.py defence360agent/utils/safe_sequence.py defence360agent/utils/serialization.py defence360agent/utils/sshutil.py defence360agent/utils/subprocess.py defence360agent/utils/support.py defence360agent/utils/threads.py defence360agent/utils/validate.py defence360agent/utils/whmcs.py defence360agent/utils/wordpress_mu_plugin.py defence360agent/utils/zipsafe.py defence360agent/wordpress/__init__.py defence360agent/wordpress/bot_protection.py defence360agent/wordpress/changelog_processor.py defence360agent/wordpress/cli.py defence360agent/wordpress/constants.py defence360agent/wordpress/exception.py defence360agent/wordpress/incident_collector.py defence360agent/wordpress/incident_parser.py defence360agent/wordpress/incident_sender.py defence360agent/wordpress/plugin.py defence360agent/wordpress/proxy_auth.py defence360agent/wordpress/site_repository.py defence360agent/wordpress/telemetry.py defence360agent/wordpress/utils.py defence360agent/wordpress/wp_rules.py imunify_core.egg-info/PKG-INFO imunify_core.egg-info/SOURCES.txt imunify_core.egg-info/dependency_links.txt imunify_core.egg-info/top_level.txtimunify_core-8.12.1-py3.11.egg-info/dependency_links.txt0000644000000000000000000000000100000000000017613 0ustar imunify_core-8.12.1-py3.11.egg-info/top_level.txt0000644000000000000000000000002000000000000016267 0ustar defence360agent